From 195c53d1f8df4f25ab21e5e1d5d6f425ab94b918 Mon Sep 17 00:00:00 2001 From: Arumeida Date: Mon, 21 Sep 2020 14:12:47 -0300 Subject: [PATCH 01/36] split deploy foreman in api_rack --- networkapi/api_rack/urls.py | 1 + networkapi/api_rack/views.py | 37 +++++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/networkapi/api_rack/urls.py b/networkapi/api_rack/urls.py index 2f9ae8841..ac64472a3 100644 --- a/networkapi/api_rack/urls.py +++ b/networkapi/api_rack/urls.py @@ -11,6 +11,7 @@ urlpatterns = patterns('', url(r'^rack/(?P\d+)/equipments/$', rack_views.RackDeployView.as_view()), + url(r'^rack/foreman/(?P\d+)/$', rack_views.RackForeman.as_view()), url(r'^rack/fabric/(?P\d+)[/]$', rack_views.RackView.as_view()), url(r'^rack/$', rack_views.RackView.as_view()), url(r'^rack/(?P\d+)/$', rack_views.RackView.as_view()), diff --git a/networkapi/api_rack/views.py b/networkapi/api_rack/views.py index bc2bc49c3..5786d6063 100644 --- a/networkapi/api_rack/views.py +++ b/networkapi/api_rack/views.py @@ -176,13 +176,10 @@ def post(self, *args, **kwargs): output = deploy_facade.deploy_config_in_equipment_synchronous(rel_filename, equip, lockvar) log.debug("equipment output: %s" % (output)) - except Exception as e: + except Exception, e: log.exception(e) raise exceptions.RackAplError(e) - # Create Foreman entries for rack switches - facade.api_foreman(rack) - datas = dict() success_map = dict() @@ -191,14 +188,40 @@ def post(self, *args, **kwargs): return Response(datas, status=status.HTTP_201_CREATED) - except exceptions.RackNumberNotFoundError as e: + except exceptions.RackNumberNotFoundError, e: log.exception(e) raise exceptions.NetworkAPIException(e) - except var_exceptions.VariableDoesNotExistException as e: + except var_exceptions.VariableDoesNotExistException, e: log.error(e) raise api_exceptions.NetworkAPIException( 'Erro buscando a variável PATH_TO_ADD_CONFIG ou REL_PATH_TO_ADD_CONFIG. Erro: %s' % e) - except Exception as e: + except Exception, e: + log.exception(e) + raise api_exceptions.NetworkAPIException(e) + + +class RackForeman (APIView): + def post(self, *args, **kwargs): + try: + log.info('RACK Foreman.') + + rack_id = kwargs.get('rack_id') + rack = facade.get_by_pk(rack_id) + # Create Foreman entries for rack switches + facade.api_foreman(rack) + raise api_exceptions.NetworkAPIException('chegou') + return Response(datas, status=status.HTTP_201_CREATED) + + except exceptions.RackNumberNotFoundError, e: + log.exception(e) + raise exceptions.NetworkAPIException(e) + + except var_exceptions.VariableDoesNotExistException, e: + log.error(e) + raise api_exceptions.NetworkAPIException( + 'Erro ao registrar o Switch no Foreman. Erro: %s' % e) + + except Exception, e: log.exception(e) raise api_exceptions.NetworkAPIException(e) From fa41ba9ba1b50dda719281032f20282ebca2725f Mon Sep 17 00:00:00 2001 From: Arumeida Date: Mon, 21 Sep 2020 14:27:46 -0300 Subject: [PATCH 02/36] Revert "split deploy foreman in api_rack" This reverts commit 195c53d1f8df4f25ab21e5e1d5d6f425ab94b918. --- networkapi/api_rack/urls.py | 1 - networkapi/api_rack/views.py | 37 +++++++----------------------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/networkapi/api_rack/urls.py b/networkapi/api_rack/urls.py index ac64472a3..2f9ae8841 100644 --- a/networkapi/api_rack/urls.py +++ b/networkapi/api_rack/urls.py @@ -11,7 +11,6 @@ urlpatterns = patterns('', url(r'^rack/(?P\d+)/equipments/$', rack_views.RackDeployView.as_view()), - url(r'^rack/foreman/(?P\d+)/$', rack_views.RackForeman.as_view()), url(r'^rack/fabric/(?P\d+)[/]$', rack_views.RackView.as_view()), url(r'^rack/$', rack_views.RackView.as_view()), url(r'^rack/(?P\d+)/$', rack_views.RackView.as_view()), diff --git a/networkapi/api_rack/views.py b/networkapi/api_rack/views.py index 5786d6063..bc2bc49c3 100644 --- a/networkapi/api_rack/views.py +++ b/networkapi/api_rack/views.py @@ -176,10 +176,13 @@ def post(self, *args, **kwargs): output = deploy_facade.deploy_config_in_equipment_synchronous(rel_filename, equip, lockvar) log.debug("equipment output: %s" % (output)) - except Exception, e: + except Exception as e: log.exception(e) raise exceptions.RackAplError(e) + # Create Foreman entries for rack switches + facade.api_foreman(rack) + datas = dict() success_map = dict() @@ -188,40 +191,14 @@ def post(self, *args, **kwargs): return Response(datas, status=status.HTTP_201_CREATED) - except exceptions.RackNumberNotFoundError, e: + except exceptions.RackNumberNotFoundError as e: log.exception(e) raise exceptions.NetworkAPIException(e) - except var_exceptions.VariableDoesNotExistException, e: + except var_exceptions.VariableDoesNotExistException as e: log.error(e) raise api_exceptions.NetworkAPIException( 'Erro buscando a variável PATH_TO_ADD_CONFIG ou REL_PATH_TO_ADD_CONFIG. Erro: %s' % e) - except Exception, e: - log.exception(e) - raise api_exceptions.NetworkAPIException(e) - - -class RackForeman (APIView): - def post(self, *args, **kwargs): - try: - log.info('RACK Foreman.') - - rack_id = kwargs.get('rack_id') - rack = facade.get_by_pk(rack_id) - # Create Foreman entries for rack switches - facade.api_foreman(rack) - raise api_exceptions.NetworkAPIException('chegou') - return Response(datas, status=status.HTTP_201_CREATED) - - except exceptions.RackNumberNotFoundError, e: - log.exception(e) - raise exceptions.NetworkAPIException(e) - - except var_exceptions.VariableDoesNotExistException, e: - log.error(e) - raise api_exceptions.NetworkAPIException( - 'Erro ao registrar o Switch no Foreman. Erro: %s' % e) - - except Exception, e: + except Exception as e: log.exception(e) raise api_exceptions.NetworkAPIException(e) From 80ca9ffd2fcd6a1e9fe80f51ab6cbea3548faf33 Mon Sep 17 00:00:00 2001 From: Arumeida Date: Mon, 21 Sep 2020 14:47:25 -0300 Subject: [PATCH 03/36] fixed split foreman deploy in api_rack --- networkapi/api_rack/views.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/networkapi/api_rack/views.py b/networkapi/api_rack/views.py index bc2bc49c3..f691b1424 100644 --- a/networkapi/api_rack/views.py +++ b/networkapi/api_rack/views.py @@ -180,9 +180,6 @@ def post(self, *args, **kwargs): log.exception(e) raise exceptions.RackAplError(e) - # Create Foreman entries for rack switches - facade.api_foreman(rack) - datas = dict() success_map = dict() @@ -203,6 +200,32 @@ def post(self, *args, **kwargs): raise api_exceptions.NetworkAPIException(e) +class RackForeman (APIView): + def post(self, *args, **kwargs): + try: + log.info('RACK Foreman.') + + rack_id = kwargs.get('rack_id') + rack = facade.get_by_pk(rack_id) + # Create Foreman entries for rack switches + facade.api_foreman(rack) + raise api_exceptions.NetworkAPIException('chegou') + return Response(datas, status=status.HTTP_201_CREATED) + + except exceptions.RackNumberNotFoundError, e: + log.exception(e) + raise exceptions.NetworkAPIException(e) + + except var_exceptions.VariableDoesNotExistException, e: + log.error(e) + raise api_exceptions.NetworkAPIException( + 'Erro ao registrar o Switch no Foreman. Erro: %s' % e) + + except Exception, e: + log.exception(e) + raise api_exceptions.NetworkAPIException(e) + + class RackConfigView(APIView): @commit_on_success From 348cccf119ec47f3de9fd29e84605da16c8806f6 Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Fri, 25 Sep 2020 16:31:32 -0300 Subject: [PATCH 04/36] test git commands --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 56cf99a05..d1576bd18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +laura amqp==1.4.9 anyjson==0.3.3 bigsuds==1.0.4 From cef9146ffc5f71f16006fc2cf2755fbf0bd9ec0d Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Fri, 25 Sep 2020 17:02:56 -0300 Subject: [PATCH 05/36] fix requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d1576bd18..56cf99a05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -laura amqp==1.4.9 anyjson==0.3.3 bigsuds==1.0.4 From 30b4b889ab38e8932bce336152e0d3cf1020bdd6 Mon Sep 17 00:00:00 2001 From: malinoski Date: Fri, 25 Sep 2020 17:08:09 -0300 Subject: [PATCH 06/36] Initial basic plugin for Junos Switch --- networkapi/plugins/Junos/__init__.py | 0 networkapi/plugins/Junos/plugin.py | 105 ++++++++++++++++ networkapi/plugins/Junos/samples/__init__.py | 0 .../samples/sample_networkapi_junos_plugin.py | 8 ++ .../Junos/samples/sample_standalone.py | 59 +++++++++ .../samples/sample_standalone_modular.py | 109 +++++++++++++++++ .../samples/sample_standalone_modular_v2.py | 115 ++++++++++++++++++ networkapi/plugins/Junos/tests.py | 13 ++ 8 files changed, 409 insertions(+) create mode 100644 networkapi/plugins/Junos/__init__.py create mode 100644 networkapi/plugins/Junos/plugin.py create mode 100644 networkapi/plugins/Junos/samples/__init__.py create mode 100644 networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py create mode 100644 networkapi/plugins/Junos/samples/sample_standalone.py create mode 100644 networkapi/plugins/Junos/samples/sample_standalone_modular.py create mode 100644 networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py create mode 100644 networkapi/plugins/Junos/tests.py diff --git a/networkapi/plugins/Junos/__init__.py b/networkapi/plugins/Junos/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/networkapi/plugins/Junos/plugin.py b/networkapi/plugins/Junos/plugin.py new file mode 100644 index 000000000..a87eef53c --- /dev/null +++ b/networkapi/plugins/Junos/plugin.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from jnpr.junos import Device +from jnpr.junos.exception import ConnectError +from networkapi.plugins.base import BasePlugin +from networkapi.plugins import exceptions +from networkapi.equipamento.models import EquipamentoAcesso + +log = logging.getLogger(__name__) + + +class Junos(BasePlugin): + + # The default ssh port connection is 830: + # https://www.juniper.net/documentation/en_US/junos-pyez/topics/task/program/junos-pyez-connection-methods.html + connect_port = 830 + + def connect(self): + """ + Return a connection object + """ + + # Collect the credentials (user and password) + if self.equipment_access is None: + try: + self.equipment_access = EquipamentoAcesso.search( + None, self.equipment, 'ssh').uniqueResult() + except Exception: + log.error('Access type %s not found for equipment %s.' % ('ssh', self.equipment.nome)) + raise exceptions.InvalidEquipmentAccessException() + device = self.equipment_access.fqdn + username = self.equipment_access.user + password = self.equipment_access.password + + try: + self.remote_conn = Device(host=device, user=username, passwd=password, port=self.connect_port) + self.remote_conn.open() + except ConnectError as e: + log.error('Could not connect to juniper host %s: %s' % (device, e)) + except IOError, e: + log.error('Could not connect to host %s: %s' % (device, e)) + raise exceptions.ConnectionException(device) + except Exception, e: + log.error('Error connecting to host %s: %s' % (device, e)) + raise Exception(e) + + def close(self): + self.remote_conn.close() + + def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): + pass + + def create_svi(self, svi_number, svi_description='no description'): + pass + + def ensure_privilege_level(self, privilege_level=None): + pass + + def remove_svi(self, svi_number): + pass + + def get_state_member(self, status): + pass + + def set_state_member(self, status): + pass + + def create_member(self, status): + pass + + def remove_member(self, status): + pass + + def get_restrictions(self, status): + pass + + def partial_update_vip(self, **kwargs): + pass + + def get_name_eqpt(self, **kwargs): + pass + + def update_pool(self, **kwargs): + pass + + def create_pool(self, **kwargs): + pass + + def delete_pool(self, **kwargs): + pass diff --git a/networkapi/plugins/Junos/samples/__init__.py b/networkapi/plugins/Junos/samples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py b/networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py new file mode 100644 index 000000000..d255d6a68 --- /dev/null +++ b/networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py @@ -0,0 +1,8 @@ +from networkapi.plugins.Junos.plugin import Junos + +plugin = Junos() +print(plugin) + + + + diff --git a/networkapi/plugins/Junos/samples/sample_standalone.py b/networkapi/plugins/Junos/samples/sample_standalone.py new file mode 100644 index 000000000..761128d13 --- /dev/null +++ b/networkapi/plugins/Junos/samples/sample_standalone.py @@ -0,0 +1,59 @@ +""" +Use example: +python networkapi/plugins/Junos/samples/sample_standalone.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' +""" + +from jnpr.junos import Device +from jnpr.junos.utils.config import Config +from jnpr.junos.exception import ConnectError, ConfigLoadError, ConnectClosedError +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('-device', help='Input host name', type=str) +parser.add_argument('-user', help='Input user name', type=str) +parser.add_argument('-password', help='Input password', type=str) +args, unknown = parser.parse_known_args() + +device = args.device +user = args.user +password = args.password + +dev = None +conf = None + +print ("Open connection ...") +try: + dev = Device(host=device, user=user, password=password, gather_facts=False) + dev.open() +except ConnectError as err: + print("Cannot connect: {0}".format(err)) + exit() + +print ("Load configuration ...") +try: + conf = Config(dev) + data = 'set interfaces gr-0/0/0 description "Some description 3 for gr-0/0/0"' + conf.load(data, format='set') # If has syntax error it will be catch and connection will be closed +except ConfigLoadError as err: + print("Cannot load configuration: {0}".format(err)) + dev.close() + exit() + +print ("Check and commit configuration ...") +try: + if conf.commit_check(): + conf.commit() + else: + print("Error - rollback configuration") + conf.rollback() +except Exception as err: + print("Some error occurred: {0}".format(err)) + +print ("Close connection ...") +try: + dev.close() +except ConnectClosedError as err: + print("Unable to close connection: {0}".format(err)) + +print ("DONE") + diff --git a/networkapi/plugins/Junos/samples/sample_standalone_modular.py b/networkapi/plugins/Junos/samples/sample_standalone_modular.py new file mode 100644 index 000000000..4d355189d --- /dev/null +++ b/networkapi/plugins/Junos/samples/sample_standalone_modular.py @@ -0,0 +1,109 @@ +""" +Use example: +python networkapi/plugins/Junos/samples/sample_standalone_modular.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' +""" + +from jnpr.junos import Device +from jnpr.junos.utils.config import Config +from jnpr.junos.exception import ConnectError, ConfigLoadError, LockError, UnlockError, ConnectClosedError +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('-device', help='Input host name', type=str) +parser.add_argument('-user', help='Input user name', type=str) +parser.add_argument('-password', help='Input password', type=str) +args, unknown = parser.parse_known_args() + + +class Junos(): + + def __init__(self, device, username, password): + + self.device = device + self.username = username + self.password = password + self.remote_conn = Device(host=device, user=username, password=password, gather_facts=False) + self.configuration = Config(self.remote_conn) + + def connect(self): + try: + self.remote_conn.open() + except ConnectError as e: + print("Cannot connect: {0}".format(e)) + except Exception, e: + print("An error occurred to connect: {0}".format(e)) + + def close(self): + try: + self.remote_conn.close() + except ConnectClosedError, e: + print("Cannot close connection: {0}".format(e)) + except Exception, e: + print("An error occurred to close connect: {0}".format(e)) + + def load_configuration(self, data, format_type): + # Ps.: If syntax error found, it will be excepted here + try: + self.configuration.load(data, format=format_type) + except ConfigLoadError as err: + print("Cannot load configuration: {0}".format(err)) + self.close() + except Exception, e: + print("An error occurred to load configuration: {0}".format(e)) + + def lock_configuration(self): + try: + self.configuration.lock() + except LockError as err: + print("Unable to lock configuration: {0}".format(err)) + self.close() + except Exception, e: + print("An error occurred to lock configuration: {0}".format(e)) + + def unlock_configuration(self): + try: + self.configuration.unlock() + except UnlockError as e: + print("Unable to unlock configuration: {0}".format(e)) + self.close() + except Exception, e: + print("An error occurred to unlock configuration: {0}".format(e)) + + def commit(self): + try: + if self.configuration.commit_check(): + self.configuration.commit() + else: + self.configuration.rollback() + print("An error occurred on commit configuration") + raise Exception + except Exception, e: + print("An error occurred to commit configuration: {0}".format(e)) + + +def main(device, username, password): + + plugin = Junos(device, username, password) + + print("Open connection ...") + plugin.connect() + + print("Locking configuration ...") + plugin.lock_configuration() + + print("Load configuration ...") + plugin.load_configuration(data='set interfaces gr-0/0/0 description "Some description 5 for gr-0/0/0"', format_type='set') + + print("Commit ...") + plugin.commit() + + print("Unlocking configuration ...") + plugin.unlock_configuration() + + print("Close connection ...") + plugin.close() + + +if __name__ == "__main__": + main(args.device, args.user, args.password) + diff --git a/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py b/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py new file mode 100644 index 000000000..05f68e600 --- /dev/null +++ b/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py @@ -0,0 +1,115 @@ +""" +Use example: +python networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' +""" + +from jnpr.junos import Device +from jnpr.junos.utils.config import Config +from jnpr.junos.exception import ConnectError, ConfigLoadError, LockError, UnlockError, ConnectClosedError +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('-device', help='Input host name', type=str) +parser.add_argument('-user', help='Input user name', type=str) +parser.add_argument('-password', help='Input password', type=str) +args, unknown = parser.parse_known_args() + + +class Junos: + + def __init__(self, device, username, password): + + self.device = device + self.username = username + self.password = password + self.remote_conn = Device(host=device, user=username, password=password, gather_facts=False) + self.configuration = Config(self.remote_conn) + + def connect(self): + try: + self.remote_conn.open() + except ConnectError as e: + print("Cannot connect: {0}".format(e)) + except Exception, e: + print("An error occurred to connect: {0}".format(e)) + + def exec_command(self, command): + print("\tLocking configuration ...") + self.__lock_configuration() + + print("\tLoad configuration ...") + self.__load_configuration(data=command, format_type='set') + + print("\tCommit ...") + self.__commit() + + print("\tUnlocking configuration ...") + self.__unlock_configuration() + + def close(self): + try: + self.remote_conn.close() + except ConnectClosedError, e: + print("Cannot close connection: {0}".format(e)) + except Exception, e: + print("An error occurred to close connect: {0}".format(e)) + + def __load_configuration(self, data, format_type): + # Ps.: If syntax error found, it will be excepted here + try: + self.configuration.load(data, format=format_type) + except ConfigLoadError as err: + print("Cannot load configuration: {0}".format(err)) + self.close() + except Exception, e: + print("An error occurred to load configuration: {0}".format(e)) + + def __lock_configuration(self): + try: + # Guarantee a clean configuration (lock then clean) + self.configuration.lock() + self.configuration.rollback() + except LockError as err: + print("Unable to lock configuration: {0}".format(err)) + self.close() + except Exception, e: + print("An error occurred to lock configuration: {0}".format(e)) + + def __unlock_configuration(self): + try: + self.configuration.unlock() + except UnlockError as e: + print("Unable to unlock configuration: {0}".format(e)) + self.close() + except Exception, e: + print("An error occurred to unlock configuration: {0}".format(e)) + + def __commit(self): + try: + if self.configuration.commit_check(): + self.configuration.commit() + else: + self.configuration.rollback() + print("An error occurred on commit configuration") + raise Exception + except Exception, e: + print("An error occurred to commit configuration: {0}".format(e)) + + +def main(device, username, password): + + plugin = Junos(device, username, password) + + print("Open connection ...") + plugin.connect() # In the future, the connect function will send an equipment (to validate maintenance) + + print("Execute configuration ...") + plugin.exec_command(command='set interfaces gr-0/0/0 description "Some description 6 for gr-0/0/0"') + + print("Close connection ...") + plugin.close() + + +if __name__ == "__main__": + main(args.device, args.user, args.password) + diff --git a/networkapi/plugins/Junos/tests.py b/networkapi/plugins/Junos/tests.py new file mode 100644 index 000000000..d9b4aebed --- /dev/null +++ b/networkapi/plugins/Junos/tests.py @@ -0,0 +1,13 @@ +from networkapi.test.test_case import NetworkApiTestCase +from networkapi.plugins.base import BasePlugin +from networkapi.plugins.Junos.plugin import Junos + + +class JunosPluginTest(NetworkApiTestCase): + + def test_create_junos_plugin_class(self): + """ + JunosPluginTest - test_create_junos_plugin_class - Tests if the class will be properly created + """ + obj = Junos() + self.assertIsInstance(obj, BasePlugin) From dc285115d2c7e7352689674c15c176bd89b9b7fa Mon Sep 17 00:00:00 2001 From: malinoski Date: Fri, 25 Sep 2020 19:14:03 -0300 Subject: [PATCH 07/36] Change functions to private --- .../samples/sample_standalone_modular_v2.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py b/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py index 05f68e600..ef90fbff2 100644 --- a/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py +++ b/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py @@ -35,16 +35,16 @@ def connect(self): def exec_command(self, command): print("\tLocking configuration ...") - self.__lock_configuration() + self._lock_configuration() print("\tLoad configuration ...") - self.__load_configuration(data=command, format_type='set') + self._load_configuration(data=command, format_type='set') print("\tCommit ...") - self.__commit() + self._commit() print("\tUnlocking configuration ...") - self.__unlock_configuration() + self._unlock_configuration() def close(self): try: @@ -54,7 +54,7 @@ def close(self): except Exception, e: print("An error occurred to close connect: {0}".format(e)) - def __load_configuration(self, data, format_type): + def _load_configuration(self, data, format_type): # Ps.: If syntax error found, it will be excepted here try: self.configuration.load(data, format=format_type) @@ -64,7 +64,7 @@ def __load_configuration(self, data, format_type): except Exception, e: print("An error occurred to load configuration: {0}".format(e)) - def __lock_configuration(self): + def _lock_configuration(self): try: # Guarantee a clean configuration (lock then clean) self.configuration.lock() @@ -75,7 +75,7 @@ def __lock_configuration(self): except Exception, e: print("An error occurred to lock configuration: {0}".format(e)) - def __unlock_configuration(self): + def _unlock_configuration(self): try: self.configuration.unlock() except UnlockError as e: @@ -84,7 +84,7 @@ def __unlock_configuration(self): except Exception, e: print("An error occurred to unlock configuration: {0}".format(e)) - def __commit(self): + def _commit(self): try: if self.configuration.commit_check(): self.configuration.commit() @@ -104,7 +104,7 @@ def main(device, username, password): plugin.connect() # In the future, the connect function will send an equipment (to validate maintenance) print("Execute configuration ...") - plugin.exec_command(command='set interfaces gr-0/0/0 description "Some description 6 for gr-0/0/0"') + plugin.exec_command(command='set interfaces gr-0/0/0 description "Some description 8 for gr-0/0/0"') print("Close connection ...") plugin.close() From 140a00c14da48d56109a60ab42acb6ebb8f14c0c Mon Sep 17 00:00:00 2001 From: Leopoldo Mauricio Date: Thu, 1 Oct 2020 15:18:58 -0300 Subject: [PATCH 08/36] Change the 'netapi_app' container image to 3.1.0 version, built with Junos-eznc==2.5.3 and its dependencies --- Makefile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8675e2c94..a3d3236a7 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # Docker image version -NETAPI_IMAGE_VERSION := 2.1.0 +NETAPI_IMAGE_VERSION := 3.1.0 # Gets git current branch curr_branch := $(shell git symbolic-ref --short -q HEAD) diff --git a/docker-compose.yml b/docker-compose.yml index de67ad1de..b68eb2196 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: netapi: container_name: netapi_app - image: globocom/networkapi:latest + image: networkapi:3.1.0 restart: always ports: - "127.0.0.1:8000:8000" From 81965da1b8dbbdeff6c51adc30a0c6cb3b0989bf Mon Sep 17 00:00:00 2001 From: Leopoldo Mauricio Date: Thu, 1 Oct 2020 15:25:02 -0300 Subject: [PATCH 09/36] Add PyEz lib and its Alpine dependencies packages. --- requirements.txt | 1 + scripts/docker/Dockerfile | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 56cf99a05..40f7b561e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,4 +36,5 @@ stompest==2.1.6 suds==0.4 schema==0.6.6 supervisor==3.3.4 +junos-eznc==2.5.3 diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile index 61499a912..970d99bd9 100644 --- a/scripts/docker/Dockerfile +++ b/scripts/docker/Dockerfile @@ -1,7 +1,9 @@ # # Base stage # -FROM alpine:latest as base +FROM alpine:3.7 as base + +RUN apk update RUN apk add --update --no-cache py2-pip \ libldap \ @@ -12,7 +14,15 @@ RUN apk add --update --no-cache py2-pip \ iputils \ bind-tools \ ncurses5-libs \ - curl + curl \ + libffi-dev \ + py-pynacl \ + libffi-dev \ + libxslt-dev \ + gcc \ + libc-dev \ + python-dev + RUN pip install --no-cache --upgrade pip RUN pip install --no-cache virtualenv From 910ff76ac8c8ee375771134d9356b4e3c6410304 Mon Sep 17 00:00:00 2001 From: malinoski Date: Tue, 6 Oct 2020 09:55:50 -0300 Subject: [PATCH 10/36] New features and improvements for junos plugin. It was included in Plugin Factory too --- .../{Junos => Juniper/JUNOS}/__init__.py | 0 networkapi/plugins/Juniper/JUNOS/plugin.py | 286 ++++++++++++++++++ .../JUNOS}/samples/__init__.py | 0 .../Juniper/JUNOS/samples/sample_command.txt | 1 + .../samples/sample_networkapi_junos_plugin.py | 66 ++++ .../JUNOS/samples/sample_standalone.py | 49 +++ .../samples/sample_standalone_modular.py} | 5 +- .../samples/sample_standalone_responses.py | 70 +++++ .../plugins/{Junos => Juniper/JUNOS}/tests.py | 12 +- networkapi/plugins/Juniper/__init__.py | 0 networkapi/plugins/Junos/plugin.py | 105 ------- .../samples/sample_networkapi_junos_plugin.py | 8 - .../Junos/samples/sample_standalone.py | 59 ---- .../samples/sample_standalone_modular.py | 109 ------- networkapi/plugins/factory.py | 3 + 15 files changed, 487 insertions(+), 286 deletions(-) rename networkapi/plugins/{Junos => Juniper/JUNOS}/__init__.py (100%) create mode 100644 networkapi/plugins/Juniper/JUNOS/plugin.py rename networkapi/plugins/{Junos => Juniper/JUNOS}/samples/__init__.py (100%) create mode 100644 networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt create mode 100644 networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py create mode 100644 networkapi/plugins/Juniper/JUNOS/samples/sample_standalone.py rename networkapi/plugins/{Junos/samples/sample_standalone_modular_v2.py => Juniper/JUNOS/samples/sample_standalone_modular.py} (93%) create mode 100644 networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_responses.py rename networkapi/plugins/{Junos => Juniper/JUNOS}/tests.py (53%) create mode 100644 networkapi/plugins/Juniper/__init__.py delete mode 100644 networkapi/plugins/Junos/plugin.py delete mode 100644 networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py delete mode 100644 networkapi/plugins/Junos/samples/sample_standalone.py delete mode 100644 networkapi/plugins/Junos/samples/sample_standalone_modular.py diff --git a/networkapi/plugins/Junos/__init__.py b/networkapi/plugins/Juniper/JUNOS/__init__.py similarity index 100% rename from networkapi/plugins/Junos/__init__.py rename to networkapi/plugins/Juniper/JUNOS/__init__.py diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py new file mode 100644 index 000000000..4143ab1e2 --- /dev/null +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import time +from exceptions import IOError +from networkapi.plugins.base import BasePlugin +from networkapi.plugins import exceptions +from networkapi.equipamento.models import EquipamentoAcesso +from jnpr.junos import Device +from jnpr.junos.utils.config import Config +from jnpr.junos.utils.start_shell import StartShell +from jnpr.junos.exception import \ + ConnectError, \ + ConfigLoadError, \ + LockError, \ + UnlockError, \ + ConnectClosedError, \ + CommitError, \ + RpcError + +log = logging.getLogger(__name__) + + +class JUNOS(BasePlugin): + + configuration = None + quantity_of_times_to_try_lock = 3 + seconds_to_wait_to_try_lock = 10 + log_tag = "[JUNOS PLUGIN]" # Used to help the syslog filtering + + def __init__(self, **kwargs): + super(JUNOS, self).__init__(connect_port=830, **kwargs) + if 'quantity_of_times_to_try_lock' in kwargs: + self.quantity_of_times_to_try_lock = kwargs.get('quantity_of_times_to_try_lock') + + if 'seconds_to_wait_to_try_lock' in kwargs: + self.seconds_to_wait_to_try_lock = kwargs.get('seconds_to_wait_to_try_lock') + + def connect(self): + + """ + Connects to equipment via ssh using PyEz and create connection with invoked shell object. + """ + + # Collect the credentials (user and password) for equipment + if self.equipment_access is None: + try: + self.equipment_access = EquipamentoAcesso.search( + None, self.equipment, 'ssh').uniqueResult() + except Exception: + log.error("{} Access type {} not found for equipment {}.".format( + self.log_tag, 'ssh', self.equipment.nome)) + raise exceptions.InvalidEquipmentAccessException() + + try: + self.remote_conn = Device( + host=self.equipment_access.fqdn, + user=self.equipment_access.user, + passwd=self.equipment_access.password, + port=self.connect_port) + self.remote_conn.open() + self.configuration = Config(self.remote_conn) + + except ConnectError as e: + log.error("{} Could not connect to juniper host {}: {}".format( + self.log_tag, self.equipment_access.fqdn, e)) + raise exceptions.ConnectionException(self.equipment_access.fqdn) + except Exception, e: + log.error("{} Error connecting to host {}: {}".format( + self.log_tag, self.equipment_access.fqdn, e)) + raise Exception(e) + + def close(self): + + """ + Disconnect to equipment via ssh using PyEz. + + Raises: + ConnectClosedError: if PyEz lib cannot close connection + IOError: if cannot connect to host + Exception: for other unhandled exceptions + """ + + try: + self.remote_conn.close() + except ConnectClosedError, e: + log.error("{} Cannot close connection on host {}: {}".format( + self.log_tag, self.equipment_access.fqdn, e)) + except Exception, e: + log.error("{} Found an unexpected error at closing connection on host {}: {}".format( + self.log_tag, self.equipment_access.fqdn, e)) + + def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): + + """ + Receives the file path (usually in /mnt/scripts/tftpboot/networkapi/generated_config/interface/) + where contains the command to be executed in the equipment + + :param str filename: must contain the full path and file name + :param str use_vrf: not used + :param str destination: not used + + :returns: + String message of result + """ + + command = None + + try: + command_file = open(filename, "r") + command = command_file.read() + except IOError, e: + log.error("{} File not found {}: {}".format(self.log_tag, filename, e)) + self.close() + except Exception, e: + log.error("{} Unexpected error occurred {}: {}".format(self.log_tag, filename, e)) + self.close() + + return self.exec_command(command) + + def exec_command(self, command, success_regex='', invalid_regex=None, error_regex=None): + + """ + Execute a junos command 'set' in the equipment. + + :param str command: junos command 'set' + :param str success_regex: not used + :param str invalid_regex: not used + :param str error_regex: not used + + :returns: + String message of result (in result_message variable) + """ + + result_message = None + + if not self.plugin_try_lock(): + result_message = "Configuration could not be locked. Anybody else locked?" + log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn)) + self.close() + return result_message + else: + log.info("{} Configuration was locked in host {} successfully".format( + self.log_tag, self.equipment_access.fqdn)) + + try: + # self.configuration.lock() # + self.configuration.rollback() # For a clean configuration + self.configuration.load(command, format='set') + self.configuration.commit_check() + self.configuration.commit() + self.configuration.unlock() + + result_message = "Configuration junos was executed successfully on host {}".format( + self.equipment_access.fqdn) + log.info("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn)) + + except LockError as e: + result_message = "Configuration could not be locked on host {}.".format(self.equipment_access.fqdn) + log.error("{} {} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + self.close() + except UnlockError as e: + result_message = "Configuration could not be unlocked on host {}. " \ + "A rollback will be executed".format(self.equipment_access.fqdn) + log.error("{} {} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + self.configuration.rollback() + self.close() + except ConfigLoadError as e: + result_message = "Configuration could not be loaded on host {}. " \ + "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) + log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + self.configuration.rollback() + self.configuration.unlock() + self.close() + except CommitError as e: + result_message = "Configuration could not be commited on host {}. " \ + "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) + log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + self.configuration.rollback() + self.configuration.unlock() + self.close() + except RpcError as e: + result_message = "Configuration database locked on host {}".format(self.equipment_access.fqdn) + log.error("{} {} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + self.close() + except Exception as e: + result_message = "An unexpected error occurred during configuration on host {}. " \ + "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) + log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + self.configuration.rollback() + self.configuration.unlock() + self.close() + + return result_message + + def ensure_privilege_level(self, privilege_level=None): + + """ + Ensure privilege level. + This function only verify is the current user is a super-user, otherwise raises an exception + """ + + # Note about StartShell timeout: duration of time in seconds must wait for the expected result + ss = StartShell(self.remote_conn, timeout=2) + ss.open() + output = ss.run('cli -c "show cli authorization"') + + # output is a tuple [bool, string], example: + # (False, u'cli -c "show cli authorization"\r\r\nCurrent user: \'root \' class \'super-user\ ....) + # This string will be parsed to get the user class: + result = output[1].split('\n') # get the target part and split it by \n + current_user_class = result[1].split("'")[3] # get the target part, split again by ' and get the target part + if current_user_class != 'super-user': + log.error("{} {} {}".format(self.log_tag, "User has no privileges", self.equipment_access.fqdn)) + self.close() + raise Exception + + def plugin_try_lock(self): + + for x in range(self.quantity_of_times_to_try_lock): + try: + self.configuration.lock() + return True + except (LockError, RpcError, Exception), e: + # Keep looping ... + log.warning( + "{} Configuration still could not be locked on host {}. " + "Automatic try in {} seconds - {}/{} ".format( + e, self.log_tag, + self.equipment_access.fqdn, + self.seconds_to_wait_to_try_lock, + x+1, + self.quantity_of_times_to_try_lock)) + time.sleep(self.seconds_to_wait_to_try_lock) + + return False + + def create_svi(self, svi_number, svi_description='no description'): + pass + + def remove_svi(self, svi_number): + pass + + def get_state_member(self, status): + pass + + def set_state_member(self, status): + pass + + def create_member(self, status): + pass + + def remove_member(self, status): + pass + + def get_restrictions(self, status): + pass + + def partial_update_vip(self, **kwargs): + pass + + def get_name_eqpt(self, **kwargs): + pass + + def update_pool(self, **kwargs): + pass + + def create_pool(self, **kwargs): + pass + + def delete_pool(self, **kwargs): + pass diff --git a/networkapi/plugins/Junos/samples/__init__.py b/networkapi/plugins/Juniper/JUNOS/samples/__init__.py similarity index 100% rename from networkapi/plugins/Junos/samples/__init__.py rename to networkapi/plugins/Juniper/JUNOS/samples/__init__.py diff --git a/networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt b/networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt new file mode 100644 index 000000000..7e210088a --- /dev/null +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt @@ -0,0 +1 @@ +set interfaces gr-0/0/0 description "Some description in file for gr-0/0/0" \ No newline at end of file diff --git a/networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py b/networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py new file mode 100644 index 000000000..2f1d28cb5 --- /dev/null +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py @@ -0,0 +1,66 @@ +""" +How to use: + +Step 1 - Edit this file variables accordingly: + host = 'HOSTNAME_OR_IP' + user = 'SSH_USER_NAME' + password = 'SSH_USER_PASSWORD' + +Step 2 - Go to another terminal and execute the test (for ANY code modification, please restart this step) + docker exec -it netapi_app python ./manage.py shell + execfile('networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py') + +Step 3 [optional] - Check more logs in networkapi: + docker exec -it netapi_app tail -f /tmp/networkapi.log + +Step 4 - Check result in equipment, exp.: + ssh SSH_USER@HOSTNAME_OR_IP + cli + show interfaces gr-0/0/0 +""" + +import logging +from networkapi.plugins.Juniper.JUNOS.plugin import JUNOS + + +# Temporary Equipment class +class EquipamentoAcesso: + def __init__(self, fqdn, user, password): + self.fqdn = fqdn + self.user = user + self.password = password + + +# Config log messages +log = logging.getLogger(__name__) +log_test_prefix = '[Junos Plugin]' +log.debug('%s Start sample' % log_test_prefix) + +# Requirements (args, equipment and plugin) +host = 'HOSTNAME_OR_IP' +user = 'SSH_USER_NAME' +password = 'SSH_USER_PASSWORD' + +# Temporary equipment access object (defined above as EquipamentoAcesso) +equipment_access = EquipamentoAcesso(host, user, password) + +# NetworkAPI junos plugin object +equip_plugin = JUNOS(equipment_access=equipment_access) + +""" OPEN CONNECTION """ +log.debug('%s Open connection ...' % log_test_prefix) +equip_plugin.connect() + +""" CHECK PRIVILEGES """ +log.debug('%s Check privilege ...' % log_test_prefix) +equip_plugin.ensure_privilege_level() + +""" EXECUTE CONFIGURATION """ +log.debug('%s Execute configuration file...' % log_test_prefix) +# equip_plugin.exec_command(command='set interfaces gr-0/0/0 description "Some description teste3 for gr-0/0/0 at "') +result = equip_plugin.copyScriptFileToConfig(filename="networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt") +log.debug('Result: %s' % result) + +""" CLOSE CONNECTION """ +log.debug('%s Close connection ...' % log_test_prefix) +equip_plugin.close() diff --git a/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone.py b/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone.py new file mode 100644 index 000000000..0fe798be9 --- /dev/null +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone.py @@ -0,0 +1,49 @@ +""" +Use example: +python networkapi/plugins/Juniper/JUNOS/samples/sample_standalone.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' +""" + +from jnpr.junos import Device +from jnpr.junos.utils.config import Config +from jnpr.junos.exception import ConnectError, ConfigLoadError, LockError, UnlockError, ConnectClosedError, CommitError +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('-device', help='Input host name', type=str) +parser.add_argument('-user', help='Input user name', type=str) +parser.add_argument('-password', help='Input password', type=str) +args, unknown = parser.parse_known_args() + +device = args.device +user = args.user +password = args.password + +print("Open connection ...") +dev = Device(host=device, user=user, password=password, gather_facts=False) +dev.open() + +print("Load config ...") +conf = Config(dev) + +print("Locking config ...") +conf.lock() + +print("Rollback config ...") +conf.rollback() + +data = 'set interfaces gr-0/0/0 description "Some description sample standalone for gr-0/0/0"' +conf.load(data, format='set') # syntax error raises exception + +print("Check ...") +conf.commit_check() + +print("Commit ...") +conf.commit() + +print("Unlocking config ...") +conf.unlock() + +print("Close connection ... ") +dev.close() + +print ("DONE") diff --git a/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py b/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_modular.py similarity index 93% rename from networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py rename to networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_modular.py index ef90fbff2..6f03ba808 100644 --- a/networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_modular.py @@ -1,6 +1,6 @@ """ Use example: -python networkapi/plugins/Junos/samples/sample_standalone_modular_v2.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' +python networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_modular.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' """ from jnpr.junos import Device @@ -104,7 +104,8 @@ def main(device, username, password): plugin.connect() # In the future, the connect function will send an equipment (to validate maintenance) print("Execute configuration ...") - plugin.exec_command(command='set interfaces gr-0/0/0 description "Some description 8 for gr-0/0/0"') + plugin.exec_command( + command='set interfaces gr-0/0/0 description "Some description sample standalone modular gr-0/0/0"') print("Close connection ...") plugin.close() diff --git a/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_responses.py b/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_responses.py new file mode 100644 index 000000000..df5957c2e --- /dev/null +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_responses.py @@ -0,0 +1,70 @@ +""" +Use example: +python networkapi/plugins/Juniper/JUNOS/samples/sample_standalone_responses.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' +""" + +from jnpr.junos import Device +from jnpr.junos.utils.config import Config +from jnpr.junos.exception import ConnectError, ConfigLoadError +from lxml import etree +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('-device', help='Input host name', type=str) +parser.add_argument('-user', help='Input user name', type=str) +parser.add_argument('-password', help='Input password', type=str) +args, unknown = parser.parse_known_args() + +device = args.device +user = args.user +password = args.password +dev = None + +try: + + print("\nOpen connection ... dev.open()") + dev = Device(host=device, user=user, password=password, gather_facts=False) + open_result = dev.open() + print(open_result.connected) # True/False + + conf = Config(dev) + data = 'set interfaces gr-0/0/0 description "Some description 3 for gr-0/0/0"' + + print("\nLock configuration ... conf.lock();") + lock_response = conf.lock() + print(lock_response) + + print("\nRollback configuration ... conf.rollback()") + rollback_response = conf.rollback() # True/False + print(rollback_response) + + print("\nLoad configuration ... conf.load(data") + load_result = conf.load(data, format='set') + print(etree.tostring(load_result, encoding='unicode', pretty_print=True)) # XML + + print("Commit check configuration ... conf.commit_check()") + commit_check_result = conf.commit_check() + print(commit_check_result) # True/False + + print("\nCommit configuration ... conf.commit()") + commit_result = conf.commit() + print(commit_result) # True/False + + print("\nUnlock configuration ... conf.unlock()") + unlock_response = conf.unlock() + print(unlock_response) + + print("\nClose connection ... .close()") + close_response = dev.close() + print(close_response) # None + # print(open_result.connected) # False/True + +except ConnectError as err: + print("Cannot connect to device: {0}".format(err)) + dev.close() +except ConfigLoadError as err: + print("Cannot load configuration: {0}".format(err)) + dev.close() +except Exception as err: + print("Some error occurred: {0}".format(err)) + dev.close() diff --git a/networkapi/plugins/Junos/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py similarity index 53% rename from networkapi/plugins/Junos/tests.py rename to networkapi/plugins/Juniper/JUNOS/tests.py index d9b4aebed..1bfa4de8a 100644 --- a/networkapi/plugins/Junos/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -1,6 +1,12 @@ +""" +How to use onlu this test: +cd GloboNetworkAPI +docker exec -it netapi_app ./fast_start_test_reusedb.sh networkapi/plugins/Juniper/JUNOS + +""" from networkapi.test.test_case import NetworkApiTestCase from networkapi.plugins.base import BasePlugin -from networkapi.plugins.Junos.plugin import Junos +from networkapi.plugins.Juniper.JUNOS.plugin import JUNOS class JunosPluginTest(NetworkApiTestCase): @@ -9,5 +15,5 @@ def test_create_junos_plugin_class(self): """ JunosPluginTest - test_create_junos_plugin_class - Tests if the class will be properly created """ - obj = Junos() - self.assertIsInstance(obj, BasePlugin) + plugin = JUNOS() + self.assertIsInstance(plugin, BasePlugin) diff --git a/networkapi/plugins/Juniper/__init__.py b/networkapi/plugins/Juniper/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/networkapi/plugins/Junos/plugin.py b/networkapi/plugins/Junos/plugin.py deleted file mode 100644 index a87eef53c..000000000 --- a/networkapi/plugins/Junos/plugin.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from jnpr.junos import Device -from jnpr.junos.exception import ConnectError -from networkapi.plugins.base import BasePlugin -from networkapi.plugins import exceptions -from networkapi.equipamento.models import EquipamentoAcesso - -log = logging.getLogger(__name__) - - -class Junos(BasePlugin): - - # The default ssh port connection is 830: - # https://www.juniper.net/documentation/en_US/junos-pyez/topics/task/program/junos-pyez-connection-methods.html - connect_port = 830 - - def connect(self): - """ - Return a connection object - """ - - # Collect the credentials (user and password) - if self.equipment_access is None: - try: - self.equipment_access = EquipamentoAcesso.search( - None, self.equipment, 'ssh').uniqueResult() - except Exception: - log.error('Access type %s not found for equipment %s.' % ('ssh', self.equipment.nome)) - raise exceptions.InvalidEquipmentAccessException() - device = self.equipment_access.fqdn - username = self.equipment_access.user - password = self.equipment_access.password - - try: - self.remote_conn = Device(host=device, user=username, passwd=password, port=self.connect_port) - self.remote_conn.open() - except ConnectError as e: - log.error('Could not connect to juniper host %s: %s' % (device, e)) - except IOError, e: - log.error('Could not connect to host %s: %s' % (device, e)) - raise exceptions.ConnectionException(device) - except Exception, e: - log.error('Error connecting to host %s: %s' % (device, e)) - raise Exception(e) - - def close(self): - self.remote_conn.close() - - def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): - pass - - def create_svi(self, svi_number, svi_description='no description'): - pass - - def ensure_privilege_level(self, privilege_level=None): - pass - - def remove_svi(self, svi_number): - pass - - def get_state_member(self, status): - pass - - def set_state_member(self, status): - pass - - def create_member(self, status): - pass - - def remove_member(self, status): - pass - - def get_restrictions(self, status): - pass - - def partial_update_vip(self, **kwargs): - pass - - def get_name_eqpt(self, **kwargs): - pass - - def update_pool(self, **kwargs): - pass - - def create_pool(self, **kwargs): - pass - - def delete_pool(self, **kwargs): - pass diff --git a/networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py b/networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py deleted file mode 100644 index d255d6a68..000000000 --- a/networkapi/plugins/Junos/samples/sample_networkapi_junos_plugin.py +++ /dev/null @@ -1,8 +0,0 @@ -from networkapi.plugins.Junos.plugin import Junos - -plugin = Junos() -print(plugin) - - - - diff --git a/networkapi/plugins/Junos/samples/sample_standalone.py b/networkapi/plugins/Junos/samples/sample_standalone.py deleted file mode 100644 index 761128d13..000000000 --- a/networkapi/plugins/Junos/samples/sample_standalone.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Use example: -python networkapi/plugins/Junos/samples/sample_standalone.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' -""" - -from jnpr.junos import Device -from jnpr.junos.utils.config import Config -from jnpr.junos.exception import ConnectError, ConfigLoadError, ConnectClosedError -import argparse - -parser = argparse.ArgumentParser() -parser.add_argument('-device', help='Input host name', type=str) -parser.add_argument('-user', help='Input user name', type=str) -parser.add_argument('-password', help='Input password', type=str) -args, unknown = parser.parse_known_args() - -device = args.device -user = args.user -password = args.password - -dev = None -conf = None - -print ("Open connection ...") -try: - dev = Device(host=device, user=user, password=password, gather_facts=False) - dev.open() -except ConnectError as err: - print("Cannot connect: {0}".format(err)) - exit() - -print ("Load configuration ...") -try: - conf = Config(dev) - data = 'set interfaces gr-0/0/0 description "Some description 3 for gr-0/0/0"' - conf.load(data, format='set') # If has syntax error it will be catch and connection will be closed -except ConfigLoadError as err: - print("Cannot load configuration: {0}".format(err)) - dev.close() - exit() - -print ("Check and commit configuration ...") -try: - if conf.commit_check(): - conf.commit() - else: - print("Error - rollback configuration") - conf.rollback() -except Exception as err: - print("Some error occurred: {0}".format(err)) - -print ("Close connection ...") -try: - dev.close() -except ConnectClosedError as err: - print("Unable to close connection: {0}".format(err)) - -print ("DONE") - diff --git a/networkapi/plugins/Junos/samples/sample_standalone_modular.py b/networkapi/plugins/Junos/samples/sample_standalone_modular.py deleted file mode 100644 index 4d355189d..000000000 --- a/networkapi/plugins/Junos/samples/sample_standalone_modular.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Use example: -python networkapi/plugins/Junos/samples/sample_standalone_modular.py -device 'HOST' -user 'SSH_USER' -password 'SSH_USER_PASSWORD' -""" - -from jnpr.junos import Device -from jnpr.junos.utils.config import Config -from jnpr.junos.exception import ConnectError, ConfigLoadError, LockError, UnlockError, ConnectClosedError -import argparse - -parser = argparse.ArgumentParser() -parser.add_argument('-device', help='Input host name', type=str) -parser.add_argument('-user', help='Input user name', type=str) -parser.add_argument('-password', help='Input password', type=str) -args, unknown = parser.parse_known_args() - - -class Junos(): - - def __init__(self, device, username, password): - - self.device = device - self.username = username - self.password = password - self.remote_conn = Device(host=device, user=username, password=password, gather_facts=False) - self.configuration = Config(self.remote_conn) - - def connect(self): - try: - self.remote_conn.open() - except ConnectError as e: - print("Cannot connect: {0}".format(e)) - except Exception, e: - print("An error occurred to connect: {0}".format(e)) - - def close(self): - try: - self.remote_conn.close() - except ConnectClosedError, e: - print("Cannot close connection: {0}".format(e)) - except Exception, e: - print("An error occurred to close connect: {0}".format(e)) - - def load_configuration(self, data, format_type): - # Ps.: If syntax error found, it will be excepted here - try: - self.configuration.load(data, format=format_type) - except ConfigLoadError as err: - print("Cannot load configuration: {0}".format(err)) - self.close() - except Exception, e: - print("An error occurred to load configuration: {0}".format(e)) - - def lock_configuration(self): - try: - self.configuration.lock() - except LockError as err: - print("Unable to lock configuration: {0}".format(err)) - self.close() - except Exception, e: - print("An error occurred to lock configuration: {0}".format(e)) - - def unlock_configuration(self): - try: - self.configuration.unlock() - except UnlockError as e: - print("Unable to unlock configuration: {0}".format(e)) - self.close() - except Exception, e: - print("An error occurred to unlock configuration: {0}".format(e)) - - def commit(self): - try: - if self.configuration.commit_check(): - self.configuration.commit() - else: - self.configuration.rollback() - print("An error occurred on commit configuration") - raise Exception - except Exception, e: - print("An error occurred to commit configuration: {0}".format(e)) - - -def main(device, username, password): - - plugin = Junos(device, username, password) - - print("Open connection ...") - plugin.connect() - - print("Locking configuration ...") - plugin.lock_configuration() - - print("Load configuration ...") - plugin.load_configuration(data='set interfaces gr-0/0/0 description "Some description 5 for gr-0/0/0"', format_type='set') - - print("Commit ...") - plugin.commit() - - print("Unlocking configuration ...") - plugin.unlock_configuration() - - print("Close connection ...") - plugin.close() - - -if __name__ == "__main__": - main(args.device, args.user, args.password) - diff --git a/networkapi/plugins/factory.py b/networkapi/plugins/factory.py index 80a1a589b..8cf4980ae 100644 --- a/networkapi/plugins/factory.py +++ b/networkapi/plugins/factory.py @@ -90,6 +90,9 @@ def get_plugin(cls, **kwargs): if re.search('CUMULUS', modelo.upper(), re.DOTALL): from .Cumulus.plugin import Cumulus return Cumulus + if re.search('JUNOS', marca.upper(), re.DOTALL): + from .Juniper.JUNOS.plugin import JUNOS + return JUNOS raise NotImplementedError('plugin not implemented') @classmethod From b29b8155cd6dbda6acebad7ecd2662b3d97fcf72 Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Tue, 6 Oct 2020 13:14:10 -0300 Subject: [PATCH 11/36] remove methods --- networkapi/plugins/Juniper/JUNOS/plugin.py | 36 ---------------------- 1 file changed, 36 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index 4143ab1e2..fc37f8fc5 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -248,39 +248,3 @@ def plugin_try_lock(self): time.sleep(self.seconds_to_wait_to_try_lock) return False - - def create_svi(self, svi_number, svi_description='no description'): - pass - - def remove_svi(self, svi_number): - pass - - def get_state_member(self, status): - pass - - def set_state_member(self, status): - pass - - def create_member(self, status): - pass - - def remove_member(self, status): - pass - - def get_restrictions(self, status): - pass - - def partial_update_vip(self, **kwargs): - pass - - def get_name_eqpt(self, **kwargs): - pass - - def update_pool(self, **kwargs): - pass - - def create_pool(self, **kwargs): - pass - - def delete_pool(self, **kwargs): - pass From 5c65f952ff576b11119ff371f1b6e6f32c674f22 Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Tue, 6 Oct 2020 13:34:33 -0300 Subject: [PATCH 12/36] update requirements - Junos-eznc --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 56cf99a05..39bb44fd3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ ipaddr==2.1.11 ipython==5.3.0 json-delta==2.0 json-spec[ip]==0.10.1 +junos-eznc==2.5.3 kombu==3.0.37 multiprocessing==2.6.2.1 MySQL-python==1.2.3 From 50c7bb599f2b441aae50bad5b49b71b29381d530 Mon Sep 17 00:00:00 2001 From: malinoski Date: Tue, 6 Oct 2020 16:02:31 -0300 Subject: [PATCH 13/36] Change 'Marca' from 'JUNOS' to 'JUNIPER' --- networkapi/plugins/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networkapi/plugins/factory.py b/networkapi/plugins/factory.py index 8cf4980ae..61229b2b2 100644 --- a/networkapi/plugins/factory.py +++ b/networkapi/plugins/factory.py @@ -90,7 +90,7 @@ def get_plugin(cls, **kwargs): if re.search('CUMULUS', modelo.upper(), re.DOTALL): from .Cumulus.plugin import Cumulus return Cumulus - if re.search('JUNOS', marca.upper(), re.DOTALL): + if re.search('JUNIPER', marca.upper(), re.DOTALL): from .Juniper.JUNOS.plugin import JUNOS return JUNOS raise NotImplementedError('plugin not implemented') From 19de0b1f36745a21f143ffad3d41d5679d10022b Mon Sep 17 00:00:00 2001 From: malinoski Date: Wed, 7 Oct 2020 10:08:57 -0300 Subject: [PATCH 14/36] Improvements for junos plugin and created basic mock test. The improvments consists to add programatic responses to functions 'connect', 'close' and 'ensure_privilege_level'. Sample test and plugin were improved with better logs --- networkapi/plugins/Juniper/JUNOS/plugin.py | 58 +++++++++++++++++-- .../samples/sample_networkapi_junos_plugin.py | 17 +++--- networkapi/plugins/Juniper/JUNOS/tests.py | 21 +++++++ 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index fc37f8fc5..5dcb30642 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -54,6 +54,9 @@ def connect(self): """ Connects to equipment via ssh using PyEz and create connection with invoked shell object. + + :returns: + True if success and False if fail """ # Collect the credentials (user and password) for equipment @@ -66,6 +69,8 @@ def connect(self): self.log_tag, 'ssh', self.equipment.nome)) raise exceptions.InvalidEquipmentAccessException() + log.info("{} Trying to connect on host {} ... ".format(self.log_tag, self.equipment_access.fqdn)) + try: self.remote_conn = Device( host=self.equipment_access.fqdn, @@ -84,17 +89,26 @@ def connect(self): self.log_tag, self.equipment_access.fqdn, e)) raise Exception(e) + if self.remote_conn.connected: + log.info("{} The connection on host {} was opened successfully!".format( + self.log_tag, self.equipment_access.fqdn)) + else: + log.error("{} An unknown error occurred to connect host {}. Connection result: {}".format( + self.log_tag, self.equipment_access.fqdn, self.remote_conn.connected)) + + return self.remote_conn.connected + def close(self): """ Disconnect to equipment via ssh using PyEz. - Raises: - ConnectClosedError: if PyEz lib cannot close connection - IOError: if cannot connect to host - Exception: for other unhandled exceptions + :returns: + True if close successfully or false if fail it """ + log.info("{} Trying to close connection on host {} ... ".format(self.log_tag, self.equipment_access.fqdn)) + try: self.remote_conn.close() except ConnectClosedError, e: @@ -104,6 +118,17 @@ def close(self): log.error("{} Found an unexpected error at closing connection on host {}: {}".format( self.log_tag, self.equipment_access.fqdn, e)) + if not self.remote_conn.connected: + log.info("{} The connection was closed successfully! Host: {} ".format( + self.log_tag, self.equipment_access.fqdn, self.remote_conn.connected)) + return True + else: + log.error( + "{} An unknown error occurred to close de connection on host {}. Connection close result: {} ".format( + self.log_tag, self.equipment_access.fqdn, self.remote_conn.connected)) + + return False + def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): """ @@ -120,6 +145,9 @@ def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): command = None + log.info("{} Trying to load configuration from file to be executed on host {} ... ".format( + self.log_tag, self.equipment_access.fqdn)) + try: command_file = open(filename, "r") command = command_file.read() @@ -130,6 +158,12 @@ def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): log.error("{} Unexpected error occurred {}: {}".format(self.log_tag, filename, e)) self.close() + if command is not None: + log.info("{} Load configuration from file {} successfully!".format(self.log_tag, filename)) + else: + log.error("{} An unknown error occurred to load configuration file {} for host {}".format( + self.log_tag, filename, self.equipment_access.fqdn)) + return self.exec_command(command) def exec_command(self, command, success_regex='', invalid_regex=None, error_regex=None): @@ -146,6 +180,9 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege String message of result (in result_message variable) """ + log.info("{} Trying to execute a configuration on host {} ... ".format( + self.log_tag, self.equipment_access.fqdn)) + result_message = None if not self.plugin_try_lock(): @@ -205,6 +242,10 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege self.configuration.unlock() self.close() + log.info( + "{} Execute configuration on host {}. Result message: '{}'".format( + self.log_tag, self.equipment_access.fqdn, result_message)) + return result_message def ensure_privilege_level(self, privilege_level=None): @@ -214,6 +255,9 @@ def ensure_privilege_level(self, privilege_level=None): This function only verify is the current user is a super-user, otherwise raises an exception """ + log.info("{} Trying to ensure privilege level for user {} on host {} ... ".format( + self.log_tag, self.equipment_access.user, self.equipment_access.fqdn)) + # Note about StartShell timeout: duration of time in seconds must wait for the expected result ss = StartShell(self.remote_conn, timeout=2) ss.open() @@ -227,7 +271,11 @@ def ensure_privilege_level(self, privilege_level=None): if current_user_class != 'super-user': log.error("{} {} {}".format(self.log_tag, "User has no privileges", self.equipment_access.fqdn)) self.close() - raise Exception + return False + else: + log.info("{} The privilege for user {}, on host {}, was satisfied! ".format( + self.log_tag, self.equipment_access.user, self.equipment_access.fqdn)) + return True def plugin_try_lock(self): diff --git a/networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py b/networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py index 2f1d28cb5..f378aa296 100644 --- a/networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_networkapi_junos_plugin.py @@ -48,19 +48,18 @@ def __init__(self, fqdn, user, password): equip_plugin = JUNOS(equipment_access=equipment_access) """ OPEN CONNECTION """ -log.debug('%s Open connection ...' % log_test_prefix) -equip_plugin.connect() +print("Open connection {}...".format(host)) +print("Connection result: {}".format(equip_plugin.connect())) """ CHECK PRIVILEGES """ -log.debug('%s Check privilege ...' % log_test_prefix) -equip_plugin.ensure_privilege_level() +print("Check privilege {}...".format(host)) +print("Privilege result: {}".format(equip_plugin.ensure_privilege_level())) """ EXECUTE CONFIGURATION """ -log.debug('%s Execute configuration file...' % log_test_prefix) +print("Execute configuration file {}...".format(host)) # equip_plugin.exec_command(command='set interfaces gr-0/0/0 description "Some description teste3 for gr-0/0/0 at "') -result = equip_plugin.copyScriptFileToConfig(filename="networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt") -log.debug('Result: %s' % result) +print("Execute configuration result: {}".format(equip_plugin.copyScriptFileToConfig(filename="networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt"))) """ CLOSE CONNECTION """ -log.debug('%s Close connection ...' % log_test_prefix) -equip_plugin.close() +print("Close connection {}...".format(host)) +print("Close connection result: {}".format(equip_plugin.close())) diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index 1bfa4de8a..28b61c36a 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -7,6 +7,7 @@ from networkapi.test.test_case import NetworkApiTestCase from networkapi.plugins.base import BasePlugin from networkapi.plugins.Juniper.JUNOS.plugin import JUNOS +from mock import patch class JunosPluginTest(NetworkApiTestCase): @@ -17,3 +18,23 @@ def test_create_junos_plugin_class(self): """ plugin = JUNOS() self.assertIsInstance(plugin, BasePlugin) + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.connect') + def test_call_connect(self, mock_plugin_function): + mock_plugin_function.assert_called() + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.close') + def test_call_close(self, mock_plugin_function): + mock_plugin_function.assert_called() + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.exec_command') + def test_call_exec_command(self, mock_plugin_function): + mock_plugin_function.assert_called() + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.copyScriptFileToConfig') + def test_call_copyScriptFileToConfig(self, mock_plugin_function): + mock_plugin_function.assert_called() + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.ensure_privilege_level') + def test_call_ensure_privilege_level(self, mock_plugin_function): + mock_plugin_function.assert_called() From b3565b18fb0757b84a7365b721c873adb8b86172 Mon Sep 17 00:00:00 2001 From: malinoski Date: Wed, 7 Oct 2020 11:16:02 -0300 Subject: [PATCH 15/36] Removed unnecessary information in log messages (log_tag) --- networkapi/plugins/Juniper/JUNOS/plugin.py | 96 ++++++++++------------ 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index 5dcb30642..f97a65e82 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -40,7 +40,6 @@ class JUNOS(BasePlugin): configuration = None quantity_of_times_to_try_lock = 3 seconds_to_wait_to_try_lock = 10 - log_tag = "[JUNOS PLUGIN]" # Used to help the syslog filtering def __init__(self, **kwargs): super(JUNOS, self).__init__(connect_port=830, **kwargs) @@ -65,11 +64,10 @@ def connect(self): self.equipment_access = EquipamentoAcesso.search( None, self.equipment, 'ssh').uniqueResult() except Exception: - log.error("{} Access type {} not found for equipment {}.".format( - self.log_tag, 'ssh', self.equipment.nome)) + log.error("Access type {} not found for equipment {}.".format('ssh', self.equipment.nome)) raise exceptions.InvalidEquipmentAccessException() - log.info("{} Trying to connect on host {} ... ".format(self.log_tag, self.equipment_access.fqdn)) + log.info("Trying to connect on host {} ... ".format(self.equipment_access.fqdn)) try: self.remote_conn = Device( @@ -81,20 +79,18 @@ def connect(self): self.configuration = Config(self.remote_conn) except ConnectError as e: - log.error("{} Could not connect to juniper host {}: {}".format( - self.log_tag, self.equipment_access.fqdn, e)) + log.error("Could not connect to juniper host {}: {}".format( + self.equipment_access.fqdn, e)) raise exceptions.ConnectionException(self.equipment_access.fqdn) except Exception, e: - log.error("{} Error connecting to host {}: {}".format( - self.log_tag, self.equipment_access.fqdn, e)) + log.error("Error connecting to host {}: {}".format(self.equipment_access.fqdn, e)) raise Exception(e) if self.remote_conn.connected: - log.info("{} The connection on host {} was opened successfully!".format( - self.log_tag, self.equipment_access.fqdn)) + log.info("The connection on host {} was opened successfully!".format(self.equipment_access.fqdn)) else: - log.error("{} An unknown error occurred to connect host {}. Connection result: {}".format( - self.log_tag, self.equipment_access.fqdn, self.remote_conn.connected)) + log.error("An unknown error occurred to connect host {}. Connection result: {}".format( + self.equipment_access.fqdn, self.remote_conn.connected)) return self.remote_conn.connected @@ -107,25 +103,24 @@ def close(self): True if close successfully or false if fail it """ - log.info("{} Trying to close connection on host {} ... ".format(self.log_tag, self.equipment_access.fqdn)) + log.info("Trying to close connection on host {} ... ".format(self.equipment_access.fqdn)) try: self.remote_conn.close() except ConnectClosedError, e: - log.error("{} Cannot close connection on host {}: {}".format( - self.log_tag, self.equipment_access.fqdn, e)) + log.error("Cannot close connection on host {}: {}".format(self.equipment_access.fqdn, e)) except Exception, e: - log.error("{} Found an unexpected error at closing connection on host {}: {}".format( - self.log_tag, self.equipment_access.fqdn, e)) + log.error("Found an unexpected error at closing connection on host {}: {}".format( + self.equipment_access.fqdn, e)) if not self.remote_conn.connected: - log.info("{} The connection was closed successfully! Host: {} ".format( - self.log_tag, self.equipment_access.fqdn, self.remote_conn.connected)) + log.info("The connection was closed successfully! Host: {} ".format( + self.equipment_access.fqdn, self.remote_conn.connected)) return True else: log.error( - "{} An unknown error occurred to close de connection on host {}. Connection close result: {} ".format( - self.log_tag, self.equipment_access.fqdn, self.remote_conn.connected)) + "An unknown error occurred to close de connection on host {}. Connection close result: {} ".format( + self.equipment_access.fqdn, self.remote_conn.connected)) return False @@ -145,24 +140,24 @@ def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): command = None - log.info("{} Trying to load configuration from file to be executed on host {} ... ".format( - self.log_tag, self.equipment_access.fqdn)) + log.info("Trying to load configuration from file to be executed on host {} ... ".format( + self.equipment_access.fqdn)) try: command_file = open(filename, "r") command = command_file.read() except IOError, e: - log.error("{} File not found {}: {}".format(self.log_tag, filename, e)) + log.error("File not found {}: {}".format(filename, e)) self.close() except Exception, e: - log.error("{} Unexpected error occurred {}: {}".format(self.log_tag, filename, e)) + log.error("Unexpected error occurred {}: {}".format(filename, e)) self.close() if command is not None: - log.info("{} Load configuration from file {} successfully!".format(self.log_tag, filename)) + log.info("Load configuration from file {} successfully!".format(filename)) else: - log.error("{} An unknown error occurred to load configuration file {} for host {}".format( - self.log_tag, filename, self.equipment_access.fqdn)) + log.error("An unknown error occurred to load configuration file {} for host {}".format( + filename, self.equipment_access.fqdn)) return self.exec_command(command) @@ -180,19 +175,18 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege String message of result (in result_message variable) """ - log.info("{} Trying to execute a configuration on host {} ... ".format( - self.log_tag, self.equipment_access.fqdn)) + log.info("Trying to execute a configuration on host {} ... ".format(self.equipment_access.fqdn)) result_message = None if not self.plugin_try_lock(): result_message = "Configuration could not be locked. Anybody else locked?" - log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn)) + log.error("{} {}".format(result_message, self.equipment_access.fqdn)) self.close() return result_message else: - log.info("{} Configuration was locked in host {} successfully".format( - self.log_tag, self.equipment_access.fqdn)) + log.info("Configuration was locked in host {} successfully".format( + self.equipment_access.fqdn)) try: # self.configuration.lock() # @@ -204,47 +198,47 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege result_message = "Configuration junos was executed successfully on host {}".format( self.equipment_access.fqdn) - log.info("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn)) + log.info("{} {}".format(result_message, self.equipment_access.fqdn)) except LockError as e: result_message = "Configuration could not be locked on host {}.".format(self.equipment_access.fqdn) - log.error("{} {} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) self.close() except UnlockError as e: result_message = "Configuration could not be unlocked on host {}. " \ "A rollback will be executed".format(self.equipment_access.fqdn) - log.error("{} {} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) self.configuration.rollback() self.close() except ConfigLoadError as e: result_message = "Configuration could not be loaded on host {}. " \ "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) self.configuration.rollback() self.configuration.unlock() self.close() except CommitError as e: result_message = "Configuration could not be commited on host {}. " \ "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) self.configuration.rollback() self.configuration.unlock() self.close() except RpcError as e: result_message = "Configuration database locked on host {}".format(self.equipment_access.fqdn) - log.error("{} {} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) self.close() except Exception as e: result_message = "An unexpected error occurred during configuration on host {}. " \ "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(self.log_tag, result_message, self.equipment_access.fqdn, e)) + log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) self.configuration.rollback() self.configuration.unlock() self.close() log.info( - "{} Execute configuration on host {}. Result message: '{}'".format( - self.log_tag, self.equipment_access.fqdn, result_message)) + "Execute configuration on host {}. Result message: '{}'".format( + self.equipment_access.fqdn, result_message)) return result_message @@ -255,8 +249,8 @@ def ensure_privilege_level(self, privilege_level=None): This function only verify is the current user is a super-user, otherwise raises an exception """ - log.info("{} Trying to ensure privilege level for user {} on host {} ... ".format( - self.log_tag, self.equipment_access.user, self.equipment_access.fqdn)) + log.info("Trying to ensure privilege level for user {} on host {} ... ".format( + self.equipment_access.user, self.equipment_access.fqdn)) # Note about StartShell timeout: duration of time in seconds must wait for the expected result ss = StartShell(self.remote_conn, timeout=2) @@ -269,12 +263,12 @@ def ensure_privilege_level(self, privilege_level=None): result = output[1].split('\n') # get the target part and split it by \n current_user_class = result[1].split("'")[3] # get the target part, split again by ' and get the target part if current_user_class != 'super-user': - log.error("{} {} {}".format(self.log_tag, "User has no privileges", self.equipment_access.fqdn)) + log.error("{} {}".format("User {} has no privileges", self.equipment_access.user, self.equipment_access.fqdn)) self.close() return False else: - log.info("{} The privilege for user {}, on host {}, was satisfied! ".format( - self.log_tag, self.equipment_access.user, self.equipment_access.fqdn)) + log.info("The privilege for user {}, on host {}, was satisfied! ".format( + self.equipment_access.user, self.equipment_access.fqdn)) return True def plugin_try_lock(self): @@ -286,13 +280,13 @@ def plugin_try_lock(self): except (LockError, RpcError, Exception), e: # Keep looping ... log.warning( - "{} Configuration still could not be locked on host {}. " - "Automatic try in {} seconds - {}/{} ".format( - e, self.log_tag, + "Configuration still could not be locked on host {}. " + "Automatic try in {} seconds - {}/{} {}".format( self.equipment_access.fqdn, self.seconds_to_wait_to_try_lock, x+1, - self.quantity_of_times_to_try_lock)) + self.quantity_of_times_to_try_lock, + e)) time.sleep(self.seconds_to_wait_to_try_lock) return False From c38e72190a8c4fa379e3f38a590aac966d777b07 Mon Sep 17 00:00:00 2001 From: malinoski Date: Wed, 7 Oct 2020 13:55:06 -0300 Subject: [PATCH 16/36] Mock tests was upgraded to assert the calls on the plugin functions --- networkapi/plugins/Juniper/JUNOS/tests.py | 48 ++++++++++++----------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index 28b61c36a..2b361d041 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -7,34 +7,36 @@ from networkapi.test.test_case import NetworkApiTestCase from networkapi.plugins.base import BasePlugin from networkapi.plugins.Juniper.JUNOS.plugin import JUNOS -from mock import patch +from mock import patch, MagicMock class JunosPluginTest(NetworkApiTestCase): def test_create_junos_plugin_class(self): - """ - JunosPluginTest - test_create_junos_plugin_class - Tests if the class will be properly created - """ plugin = JUNOS() self.assertIsInstance(plugin, BasePlugin) - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.connect') - def test_call_connect(self, mock_plugin_function): - mock_plugin_function.assert_called() - - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.close') - def test_call_close(self, mock_plugin_function): - mock_plugin_function.assert_called() - - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.exec_command') - def test_call_exec_command(self, mock_plugin_function): - mock_plugin_function.assert_called() - - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.copyScriptFileToConfig') - def test_call_copyScriptFileToConfig(self, mock_plugin_function): - mock_plugin_function.assert_called() - - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS.ensure_privilege_level') - def test_call_ensure_privilege_level(self, mock_plugin_function): - mock_plugin_function.assert_called() + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) + def test_connect(self, mock_junos_plugin): + mock_junos_plugin.connect() + mock_junos_plugin.connect.assert_called_with() + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) + def test_call_close(self, mock_junos_plugin): + mock_junos_plugin.close() + mock_junos_plugin.close.assert_called_with() + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) + def test_call_exec_command(self, mock_junos_plugin): + mock_junos_plugin.exec_command("any command") + mock_junos_plugin.exec_command.assert_called_with("any command") + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) + def test_call_copyScriptFileToConfig(self, mock_junos_plugin): + mock_junos_plugin.copyScriptFileToConfig("any file path") + mock_junos_plugin.copyScriptFileToConfig.assert_called_with("any file path") + + @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) + def test_call_ensure_privilege_level(self, mock_junos_plugin): + mock_junos_plugin.ensure_privilege_level() + mock_junos_plugin.ensure_privilege_level.assert_called_with() From e44c730e459a38ee9990e86166a10d1bdbaf3bdc Mon Sep 17 00:00:00 2001 From: malinoski Date: Fri, 9 Oct 2020 10:16:25 -0300 Subject: [PATCH 17/36] Mock tests was improved to reuse a mock class (equipment) and mock success inside target function (connect and exec_command) --- networkapi/plugins/Juniper/JUNOS/tests.py | 89 +++++++++++++++++++---- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index 2b361d041..19ff69310 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -1,9 +1,3 @@ -""" -How to use onlu this test: -cd GloboNetworkAPI -docker exec -it netapi_app ./fast_start_test_reusedb.sh networkapi/plugins/Juniper/JUNOS - -""" from networkapi.test.test_case import NetworkApiTestCase from networkapi.plugins.base import BasePlugin from networkapi.plugins.Juniper.JUNOS.plugin import JUNOS @@ -12,25 +6,88 @@ class JunosPluginTest(NetworkApiTestCase): - def test_create_junos_plugin_class(self): + """ + Junos plugin tests + + How to use: + cd GloboNetworkAPI + docker exec -it netapi_app ./fast_start_test_reusedb.sh networkapi/plugins/Juniper/JUNOS + + General notes: + - autospec=True: responds to methods that actually exist in the real class + - assert_called_once_with() assert if an important step was called + """ + + @patch('networkapi.equipamento.models.EquipamentoAcesso', autospec=True) + def setUp(self, mock_equipment_access): + + """ + Equipment mock is a fake class used in specific test cases below + """ + + mock_equipment_access.fqdn = 'any fqdn' + mock_equipment_access.user = 'any user' + mock_equipment_access.password = 'any password' + self.mock_equipment_access = mock_equipment_access + + def test_create_junos_with_super_class(self): plugin = JUNOS() self.assertIsInstance(plugin, BasePlugin) - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) - def test_connect(self, mock_junos_plugin): - mock_junos_plugin.connect() - mock_junos_plugin.connect.assert_called_with() + @patch('networkapi.plugins.Juniper.JUNOS.plugin.Device', autospec=True) + def test_connect_success(self, mock_device): + + """ + Simulate connect success using Junos Device mock. + + Note: All internal functions in Device Class are mocked, raising no exceptions on it + + Warning: For an unknown reason was not possible to use + 'jnpr.junos.Device' instead 'networkapi.plugins.Juniper.JUNOS.plugin.Device' + """ + + # Mocking result + mock_device.return_value.connected = True + + # Close real test + plugin = JUNOS(equipment_access=self.mock_equipment_access) + connection_response = plugin.connect() + + # Asserts + plugin.remote_conn.open.assert_called_with() + self.assertIsNotNone(plugin.configuration) + self.assertEqual(connection_response, True) + + @patch('jnpr.junos.utils.config.Config', autospec=True) + def test_exec_command_success(self, mock_config): + + """ + This test asserts the success of the complete workflow of executing any command. + Note: All internal functions in Config Class are mocked, raising no exceptions on it + """ + + # Mocking result + plugin = JUNOS(equipment_access=self.mock_equipment_access) + plugin.plugin_try_lock = MagicMock() + plugin.plugin_try_lock.return_value = True + plugin.configuration = mock_config + + # Close real test + exec_command_response = plugin.exec_command("any command") + + # Assert + plugin.configuration.rollback.assert_called_once_with() + plugin.configuration.load.assert_called_once_with("any command", format='set') + plugin.configuration.commit_check.assert_called_once_with() + plugin.configuration.commit.assert_called_once_with() + plugin.configuration.unlock.assert_called_once_with() + self.assertIsNotNone(exec_command_response) @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) def test_call_close(self, mock_junos_plugin): mock_junos_plugin.close() mock_junos_plugin.close.assert_called_with() - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) - def test_call_exec_command(self, mock_junos_plugin): - mock_junos_plugin.exec_command("any command") - mock_junos_plugin.exec_command.assert_called_with("any command") - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) def test_call_copyScriptFileToConfig(self, mock_junos_plugin): mock_junos_plugin.copyScriptFileToConfig("any file path") From 86af75df3dd8a7e81844ac90b7cb0cc1265fdc25 Mon Sep 17 00:00:00 2001 From: malinoski Date: Tue, 13 Oct 2020 16:43:32 -0300 Subject: [PATCH 18/36] The method connection() was not returning errors properly --- networkapi/plugins/Juniper/JUNOS/plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index f97a65e82..95a223170 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -69,6 +69,7 @@ def connect(self): log.info("Trying to connect on host {} ... ".format(self.equipment_access.fqdn)) + result = False try: self.remote_conn = Device( host=self.equipment_access.fqdn, @@ -78,13 +79,13 @@ def connect(self): self.remote_conn.open() self.configuration = Config(self.remote_conn) + if self.remote_conn.connected: + result = True except ConnectError as e: log.error("Could not connect to juniper host {}: {}".format( self.equipment_access.fqdn, e)) - raise exceptions.ConnectionException(self.equipment_access.fqdn) except Exception, e: log.error("Error connecting to host {}: {}".format(self.equipment_access.fqdn, e)) - raise Exception(e) if self.remote_conn.connected: log.info("The connection on host {} was opened successfully!".format(self.equipment_access.fqdn)) @@ -92,7 +93,7 @@ def connect(self): log.error("An unknown error occurred to connect host {}. Connection result: {}".format( self.equipment_access.fqdn, self.remote_conn.connected)) - return self.remote_conn.connected + return result def close(self): From 67f0a5c2005f9b5588f4e3d1207888742bade9cf Mon Sep 17 00:00:00 2001 From: Leopoldo Mauricio Date: Tue, 13 Oct 2020 18:57:05 -0300 Subject: [PATCH 19/36] Change the networkapi image name --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index b68eb2196..de67ad1de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: netapi: container_name: netapi_app - image: networkapi:3.1.0 + image: globocom/networkapi:latest restart: always ports: - "127.0.0.1:8000:8000" From 89bbc769984ab4fc63e2a6c11654ff4f63b49036 Mon Sep 17 00:00:00 2001 From: Leopoldo Mauricio Date: Thu, 15 Oct 2020 12:26:25 -0300 Subject: [PATCH 20/36] Includes juniper models that will use the Junos plugin --- networkapi/plugins/factory.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/networkapi/plugins/factory.py b/networkapi/plugins/factory.py index 61229b2b2..86790383d 100644 --- a/networkapi/plugins/factory.py +++ b/networkapi/plugins/factory.py @@ -91,8 +91,10 @@ def get_plugin(cls, **kwargs): from .Cumulus.plugin import Cumulus return Cumulus if re.search('JUNIPER', marca.upper(), re.DOTALL): - from .Juniper.JUNOS.plugin import JUNOS - return JUNOS + if re.search('QFX10008', modelo.upper(), re.DOTALL) \ + or re.search('QFX5120-48T', modelo.upper(), re.DOTALL): + from .Juniper.JUNOS.plugin import JUNOS + return JUNOS raise NotImplementedError('plugin not implemented') @classmethod From cc5f15de81564dba27b2361693d256b0cd337d88 Mon Sep 17 00:00:00 2001 From: Leopoldo Mauricio Date: Fri, 16 Oct 2020 14:36:15 -0300 Subject: [PATCH 21/36] Add dependencies libraries to enable junos-eznc --- requirements.txt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1af714a65..eeafa2ce6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,5 +37,20 @@ stompest==2.1.6 suds==0.4 schema==0.6.6 supervisor==3.3.4 -junos-eznc==2.5.3 - +Jinja2==2.9 +MarkupSafe==1.1.1 +PyYAML==5.3 +certifi==2018.10.15 +chardet==3.0.4 +gtextfsm==0.2.1 +lxml==4.5.2 +ncclient==0.6.3 +ntc-templates==1.0.0 +pyparsing==2.4.7 +pyserial==3.4 +scp==0.13.2 +selectors2==2.0.2 +terminal==0.4.0 +transitions==0.6.9 +urllib3==1.24.1 +yamlordereddictloader==0.4.0 From 1b6727c898b7c9a4245a9b6d46fa18df714908ca Mon Sep 17 00:00:00 2001 From: Leopoldo Mauricio Date: Fri, 16 Oct 2020 14:40:50 -0300 Subject: [PATCH 22/36] Add dependencies libraries to enable junos-eznc --- requirements_test.txt | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 550329e2d..68d2bad27 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -31,4 +31,22 @@ PyYAML==3.10 Sphinx==1.2.2 sphinx-rtd-theme==0.1.6 sure==1.2.7 -urllib3==1.21.1 +#urllib3==1.21.1 + +Jinja2==2.9 +MarkupSafe==1.1.1 +PyYAML==5.3 +certifi==2018.10.15 +chardet==3.0.4 +gtextfsm==0.2.1 +lxml==4.5.2 +ncclient==0.6.3 +ntc-templates==1.0.0 +pyparsing==2.4.7 +pyserial==3.4 +scp==0.13.2 +selectors2==2.0.2 +terminal==0.4.0 +transitions==0.6.9 +urllib3==1.24.1 +yamlordereddictloader==0.4.0 From 210c92d4e2950a2bbef097f880ed24ee46685a25 Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Tue, 20 Oct 2020 15:29:30 -0300 Subject: [PATCH 23/36] create get method to ASNEquipment --- networkapi/api_asn/models.py | 12 +++++- networkapi/api_asn/v4/facade.py | 73 +++++++++++++++++++++++++++++++++ networkapi/api_asn/v4/urls.py | 6 +++ networkapi/api_asn/v4/views.py | 52 +++++++++++++++++++++++ 4 files changed, 141 insertions(+), 2 deletions(-) diff --git a/networkapi/api_asn/models.py b/networkapi/api_asn/models.py index f88d510c8..036f21065 100644 --- a/networkapi/api_asn/models.py +++ b/networkapi/api_asn/models.py @@ -138,7 +138,7 @@ class Meta(BaseModel.Meta): managed = True @classmethod - def get_by_pk(cls, id): + def get_by_pk(cls, ids=None, asn=None, equipment=None): """Get AsnEquipment by id. :return: AsnEquipment. @@ -148,7 +148,15 @@ def get_by_pk(cls, id): :raise OperationalError: Lock wait timeout exceeded """ try: - return AsnEquipment.objects.get(id=id) + if ids: + return AsnEquipment.objects.get(id=ids) + elif asn: + return AsnEquipment.objects.get(asn__id=int(asn)) + elif equipment: + return AsnEquipment.objects.get(equipment__id=equipment) + + return AsnEquipment.objects.all() + except ObjectDoesNotExist: cls.log.error(u'AsnEquipment not found. pk {}'.format(id)) raise exceptions.AsnEquipmentNotFoundError(id) diff --git a/networkapi/api_asn/v4/facade.py b/networkapi/api_asn/v4/facade.py index a3c935ced..8ef17d9f2 100644 --- a/networkapi/api_asn/v4/facade.py +++ b/networkapi/api_asn/v4/facade.py @@ -4,6 +4,7 @@ from django.core.exceptions import FieldError from networkapi.api_asn.models import Asn +from networkapi.api_asn.models import AsnEquipment from networkapi.api_asn.v4 import exceptions from networkapi.api_asn.v4.exceptions import AsnErrorV4 from networkapi.api_asn.v4.exceptions import AsnNotFoundError, AsnError @@ -116,3 +117,75 @@ def delete_as(as_ids): except Exception, e: raise NetworkAPIException(str(e)) + +def get_as_equipment_by_search(search=dict()): + """Return a list of ASEquipment's by dict.""" + + try: + as_equipment = AsnEquipment.get_by_pk() + as_map = build_query_to_datatable_v3(as_equipment, search) + except FieldError as e: + raise ValidationAPIException(str(e)) + except Exception as e: + raise NetworkAPIException(str(e)) + else: + return as_map + + +def get_as_equipment_by_id(as_equipment_id=None): + """Return an ASEquipment by id. + + Args: + as_equipment_id: Id of ASEquipment + + """ + + try: + as_equipment_list = list() + for asn in as_equipment_id: + as_equipment = AsnEquipment.get_by_pk(ids=asn) + as_equipment_list.append(as_equipment) + except AsnNotFoundError, e: + raise exceptions.AsnDoesNotExistException(str(e)) + + return as_equipment_list + + +def get_as_equipment_by_asn(asn_id=None): + """Return an ASEquipment by asn id. + + Args: + asn_id: Id of ASN + + """ + + try: + as_equipment_list = list() + for asn in asn_id: + as_equipment = AsnEquipment.get_by_pk(asn=asn) + as_equipment_list.append(as_equipment) + + except AsnNotFoundError, e: + raise exceptions.AsnDoesNotExistException(str(e)) + + return as_equipment_list + + +def get_as_equipment_by_equip(equipment_id=None): + """Return an ASEquipment by equipment id. + + Args: + equipment_id: Id of Equipment + + """ + + try: + as_equipment_list = list() + for equip in equipment_id: + as_equipment = AsnEquipment.get_by_pk(equipment=equip) + as_equipment_list.append(as_equipment) + + except AsnNotFoundError, e: + raise exceptions.AsnDoesNotExistException(str(e)) + + return as_equipment_list diff --git a/networkapi/api_asn/v4/urls.py b/networkapi/api_asn/v4/urls.py index a8763e2b1..860f5e939 100644 --- a/networkapi/api_asn/v4/urls.py +++ b/networkapi/api_asn/v4/urls.py @@ -6,6 +6,12 @@ urlpatterns = patterns( '', + url(r'^asnequipment/((?P[;\w]+)/)?$', + views.AsEquipmentDBView.as_view()), + url(r'^asnequipment/asn/((?P[;\w]+)/)?$', + views.AsEquipmentDBView.as_view()), + url(r'^asnequipment/equipment/((?P[;\w]+)/)?$', + views.AsEquipmentDBView.as_view()), url(r'^as/((?P[;\w]+)/)?$', views.AsDBView.as_view()), ) diff --git a/networkapi/api_asn/v4/views.py b/networkapi/api_asn/v4/views.py index 6d1314bdb..1f4d0327f 100644 --- a/networkapi/api_asn/v4/views.py +++ b/networkapi/api_asn/v4/views.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Create your views here. +import logging from django.db.transaction import commit_on_success from rest_framework import status from rest_framework.permissions import IsAuthenticated @@ -19,6 +20,9 @@ from networkapi.util.json_validate import raise_json_validate +log = logging.getLogger(__name__) + + class AsDBView(CustomAPIView): @logs_method_apiview @@ -106,3 +110,51 @@ def delete(self, request, *args, **kwargs): facade.delete_as(obj_ids) return Response({}, status=status.HTTP_200_OK) + + +class AsEquipmentDBView(CustomAPIView): + + @logs_method_apiview + @raise_json_validate('') + @permission_classes_apiview((IsAuthenticated, Read)) + @prepare_search + def get(self, request, *args, **kwargs): + """Returns a list of AS's by ids ou dict.""" + + if not kwargs.get('obj_ids') and\ + not kwargs.get('asn_ids') and\ + not kwargs.get('equip_ids'): + obj_model = facade.get_as_equipment_by_search(self.search) + as_s = obj_model['query_set'] + only_main_property = False + else: + obj_model = None + only_main_property = True + if kwargs.get('obj_ids'): + as_ids = kwargs.get('obj_ids').split(';') + as_s = facade.get_as_equipment_by_id(as_ids) + elif kwargs.get('asn_ids'): + as_ids = kwargs.get('asn_ids').split(';') + as_s = facade.get_as_equipment_by_asn(as_ids) + elif kwargs.get('equip_ids'): + as_ids = kwargs.get('equip_ids').split(';') + as_s = facade.get_as_equipment_by_equip(as_ids) + + serializer_as = serializers.AsnEquipmentV4Serializer( + as_s, + many=True, + fields=self.fields, + include=self.include, + exclude=self.exclude, + kind=self.kind + ) + + data = render_to_json( + serializer_as, + main_property='asn_equipments', + obj_model=obj_model, + request=request, + only_main_property=only_main_property + ) + + return Response(data, status=status.HTTP_200_OK) From 6d63009ac234c9011aa26be6844530e293d7d0a3 Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Tue, 20 Oct 2020 16:19:49 -0300 Subject: [PATCH 24/36] create post method to ASNEquipment --- networkapi/api_asn/v4/facade.py | 24 ++++++++++++++++ .../api_asn/v4/specs/asn_equipment_post.json | 28 +++++++++++++++++++ networkapi/api_asn/v4/views.py | 17 ++++++++++- networkapi/settings.py | 4 +++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 networkapi/api_asn/v4/specs/asn_equipment_post.json diff --git a/networkapi/api_asn/v4/facade.py b/networkapi/api_asn/v4/facade.py index 8ef17d9f2..a5cdee753 100644 --- a/networkapi/api_asn/v4/facade.py +++ b/networkapi/api_asn/v4/facade.py @@ -189,3 +189,27 @@ def get_as_equipment_by_equip(equipment_id=None): raise exceptions.AsnDoesNotExistException(str(e)) return as_equipment_list + + +def create_asn_equipment(asn_equipment): + """Create ASNEquipment.""" + + try: + asn_equipment_list = list() + + for equipment in asn_equipment.get("equipment"): + obj = dict() + obj["equipment"] = equipment + obj["asn"] = asn_equipment.get("asn") + as_obj = AsnEquipment() + as_obj.create_v4(obj) + asn_equipment_list.append({'id': as_obj.id}) + + except AsnErrorV4, e: + raise ValidationAPIException(str(e)) + except ValidationAPIException, e: + raise ValidationAPIException(str(e)) + except Exception, e: + raise NetworkAPIException(str(e)) + + return asn_equipment_list diff --git a/networkapi/api_asn/v4/specs/asn_equipment_post.json b/networkapi/api_asn/v4/specs/asn_equipment_post.json new file mode 100644 index 000000000..19ef79eac --- /dev/null +++ b/networkapi/api_asn/v4/specs/asn_equipment_post.json @@ -0,0 +1,28 @@ +{ + "title": "ASNEquipment Post", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ + "asn_equipment" + ], + "properties": { + "networks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "asn": { + "type": "integer" + }, + "equipment": { + "type": "array" + } + }, + "required": [ + "asn", + "equipment" + ] + } + } + } +} diff --git a/networkapi/api_asn/v4/views.py b/networkapi/api_asn/v4/views.py index 1f4d0327f..486303fac 100644 --- a/networkapi/api_asn/v4/views.py +++ b/networkapi/api_asn/v4/views.py @@ -151,10 +151,25 @@ def get(self, request, *args, **kwargs): data = render_to_json( serializer_as, - main_property='asn_equipments', + main_property='asn_equipment', obj_model=obj_model, request=request, only_main_property=only_main_property ) return Response(data, status=status.HTTP_200_OK) + + @logs_method_apiview + @raise_json_validate('asn_equipment_post_v4') + @permission_classes_apiview((IsAuthenticated, Write)) + @commit_on_success + def post(self, request, *args, **kwargs): + """Create new ASNEquipment.""" + + as_s = request.DATA + json_validate(SPECS.get('asn_equipment_post_v4')).validate(as_s) + response = list() + for as_ in as_s['asn_equipment']: + response = facade.create_asn_equipment(as_) + + return Response(response, status=status.HTTP_201_CREATED) diff --git a/networkapi/settings.py b/networkapi/settings.py index 80b496116..9e30d6775 100644 --- a/networkapi/settings.py +++ b/networkapi/settings.py @@ -546,6 +546,10 @@ def local_files(path): PROJECT_ROOT_PATH, 'api_asn/v4/specs/as_put.json' ), + 'asn_equipment_post_v4': os.path.join( + PROJECT_ROOT_PATH, + 'api_asn/v4/specs/asn_equipment_post.json' + ), 'equipment_post_v4': os.path.join( PROJECT_ROOT_PATH, 'api_equipment/v4/specs/equipment_post.json' From ac9734b20592da7d8af0cf378c8cb966f1f8d476 Mon Sep 17 00:00:00 2001 From: malinoski Date: Wed, 21 Oct 2020 09:06:07 -0300 Subject: [PATCH 25/36] Junos test file updated with close connection successfully --- networkapi/plugins/Juniper/JUNOS/tests.py | 31 ++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index 19ff69310..837c0c9fe 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -31,6 +31,11 @@ def setUp(self, mock_equipment_access): self.mock_equipment_access = mock_equipment_access def test_create_junos_with_super_class(self): + + """ + test_create_junos_with_super_class - test if Junos plugin is the BasePlugin + """ + plugin = JUNOS() self.assertIsInstance(plugin, BasePlugin) @@ -38,7 +43,7 @@ def test_create_junos_with_super_class(self): def test_connect_success(self, mock_device): """ - Simulate connect success using Junos Device mock. + test_connect_success - Simulate connect success using Junos Device mock. Note: All internal functions in Device Class are mocked, raising no exceptions on it @@ -62,7 +67,7 @@ def test_connect_success(self, mock_device): def test_exec_command_success(self, mock_config): """ - This test asserts the success of the complete workflow of executing any command. + test_exec_command_success - This test asserts the success of the complete workflow of executing any command. Note: All internal functions in Config Class are mocked, raising no exceptions on it """ @@ -83,10 +88,24 @@ def test_exec_command_success(self, mock_config): plugin.configuration.unlock.assert_called_once_with() self.assertIsNotNone(exec_command_response) - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) - def test_call_close(self, mock_junos_plugin): - mock_junos_plugin.close() - mock_junos_plugin.close.assert_called_with() + @patch('jnpr.junos.Device') + def test_call_close_success(self, mock_device): + + """ + test_call_close_success - Test if the plugin junos plugin close a connection with the expected asserts + """ + + # Mocking + mock_device.connected = False + plugin = JUNOS(equipment_access=self.mock_equipment_access) + plugin.remote_conn = mock_device + + # Close to a real test: + close_response = plugin.close() + + # Asserts + plugin.remote_conn.close.assert_called_once_with() + self.assertTrue(close_response, True) @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) def test_call_copyScriptFileToConfig(self, mock_junos_plugin): From b223fd6ef8991f89dbddf0aafed1aa68cb062fc2 Mon Sep 17 00:00:00 2001 From: malinoski Date: Wed, 21 Oct 2020 12:57:28 -0300 Subject: [PATCH 26/36] Plugin refactored to raise exception instead failed return messages --- networkapi/plugins/Juniper/JUNOS/plugin.py | 214 +++++++++++---------- 1 file changed, 108 insertions(+), 106 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index 95a223170..21fcaa62e 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -36,7 +36,6 @@ class JUNOS(BasePlugin): - configuration = None quantity_of_times_to_try_lock = 3 seconds_to_wait_to_try_lock = 10 @@ -55,12 +54,12 @@ def connect(self): Connects to equipment via ssh using PyEz and create connection with invoked shell object. :returns: - True if success and False if fail + True on success or raise an exception on any fail (will NOT return a false result, due project decision). """ - # Collect the credentials (user and password) for equipment if self.equipment_access is None: try: + # Collect the credentials (user and password) for equipment self.equipment_access = EquipamentoAcesso.search( None, self.equipment, 'ssh').uniqueResult() except Exception: @@ -69,7 +68,6 @@ def connect(self): log.info("Trying to connect on host {} ... ".format(self.equipment_access.fqdn)) - result = False try: self.remote_conn = Device( host=self.equipment_access.fqdn, @@ -80,20 +78,16 @@ def connect(self): self.configuration = Config(self.remote_conn) if self.remote_conn.connected: - result = True + log.info("The connection on host {} was opened successfully!".format(self.equipment_access.fqdn)) + return True + except ConnectError as e: - log.error("Could not connect to juniper host {}: {}".format( - self.equipment_access.fqdn, e)) + log.error("Could not connect to Juniper host {}: {}".format(self.equipment_access.fqdn, e)) + raise ConnectError + except Exception, e: log.error("Error connecting to host {}: {}".format(self.equipment_access.fqdn, e)) - - if self.remote_conn.connected: - log.info("The connection on host {} was opened successfully!".format(self.equipment_access.fqdn)) - else: - log.error("An unknown error occurred to connect host {}. Connection result: {}".format( - self.equipment_access.fqdn, self.remote_conn.connected)) - - return result + raise Exception def close(self): @@ -101,29 +95,23 @@ def close(self): Disconnect to equipment via ssh using PyEz. :returns: - True if close successfully or false if fail it + True on success or raise an exception on any fail (will NOT return a false result, due project decision). """ log.info("Trying to close connection on host {} ... ".format(self.equipment_access.fqdn)) try: self.remote_conn.close() + log.info("The connection was closed successfully on host {}!".format(self.equipment_access.fqdn)) + return True + except ConnectClosedError, e: log.error("Cannot close connection on host {}: {}".format(self.equipment_access.fqdn, e)) - except Exception, e: - log.error("Found an unexpected error at closing connection on host {}: {}".format( - self.equipment_access.fqdn, e)) + raise ConnectClosedError - if not self.remote_conn.connected: - log.info("The connection was closed successfully! Host: {} ".format( - self.equipment_access.fqdn, self.remote_conn.connected)) - return True - else: - log.error( - "An unknown error occurred to close de connection on host {}. Connection close result: {} ".format( - self.equipment_access.fqdn, self.remote_conn.connected)) - - return False + except Exception, e: + log.error("Unexpected error at closing connection on host {}: {}".format(self.equipment_access.fqdn, e)) + raise Exception def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): @@ -136,31 +124,26 @@ def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): :param str destination: not used :returns: - String message of result + Returns a success message, otherwise, raise an exception. That means will NOT return a false result. """ - command = None - - log.info("Trying to load configuration from file to be executed on host {} ... ".format( - self.equipment_access.fqdn)) + log.info("Trying to load file configuration for host {} ...".format(self.equipment_access.fqdn)) try: command_file = open(filename, "r") command = command_file.read() + log.info("Load configuration from file {} successfully!".format(filename)) + return self.exec_command(command) + except IOError, e: log.error("File not found {}: {}".format(filename, e)) self.close() + raise IOError + except Exception, e: log.error("Unexpected error occurred {}: {}".format(filename, e)) self.close() - - if command is not None: - log.info("Load configuration from file {} successfully!".format(filename)) - else: - log.error("An unknown error occurred to load configuration file {} for host {}".format( - filename, self.equipment_access.fqdn)) - - return self.exec_command(command) + raise Exception def exec_command(self, command, success_regex='', invalid_regex=None, error_regex=None): @@ -173,121 +156,140 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege :param str error_regex: not used :returns: - String message of result (in result_message variable) + Returns a success message, otherwise, raise an exception. That means will NOT return a false result. """ log.info("Trying to execute a configuration on host {} ... ".format(self.equipment_access.fqdn)) - result_message = None - - if not self.plugin_try_lock(): - result_message = "Configuration could not be locked. Anybody else locked?" - log.error("{} {}".format(result_message, self.equipment_access.fqdn)) - self.close() - return result_message - else: - log.info("Configuration was locked in host {} successfully".format( - self.equipment_access.fqdn)) - try: - # self.configuration.lock() # - self.configuration.rollback() # For a clean configuration + self.__try_lock() + self.configuration.rollback() self.configuration.load(command, format='set') self.configuration.commit_check() self.configuration.commit() self.configuration.unlock() - result_message = "Configuration junos was executed successfully on host {}".format( - self.equipment_access.fqdn) - log.info("{} {}".format(result_message, self.equipment_access.fqdn)) + result_message = "Configuration junos was executed successfully on {}".format(self.equipment_access.fqdn) + log.info(result_message) + return result_message except LockError as e: - result_message = "Configuration could not be locked on host {}.".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) + log.error("Couldn't lock host {} " + "(close will be tried for safety): {}".format(self.equipment_access.fqdn, e)) self.close() + raise LockError + except UnlockError as e: - result_message = "Configuration could not be unlocked on host {}. " \ - "A rollback will be executed".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) + log.error("Couldn't unlock host {} " + "(rollback and close will be tried for safety): {}".format(self.equipment_access.fqdn, e)) self.configuration.rollback() self.close() + raise UnlockError + except ConfigLoadError as e: - result_message = "Configuration could not be loaded on host {}. " \ - "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) + log.error("Couldn't load configuration on host {} " + "(rollback, unlock and close will be tried for safety): {}".format(self.equipment_access.fqdn, e)) self.configuration.rollback() self.configuration.unlock() self.close() + raise ConfigLoadError + except CommitError as e: - result_message = "Configuration could not be commited on host {}. " \ - "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) + log.error("Couldn't commit configuration on {} " + "(rollback, unlock and close will be tried for safety): {}".format(self.equipment_access.fqdn, e)) self.configuration.rollback() self.configuration.unlock() self.close() + raise CommitError + except RpcError as e: - result_message = "Configuration database locked on host {}".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) + log.error("A remote procedure call exception occurred on host {} " + "(close will be tried for safety): {}".format(self.equipment_access.fqdn, e)) self.close() + raise RpcError + except Exception as e: - result_message = "An unexpected error occurred during configuration on host {}. " \ - "A rollback and unlock will be executed.".format(self.equipment_access.fqdn) - log.error("{} {} {}".format(result_message, self.equipment_access.fqdn, e)) + log.error("An exception error occurred during configuration execution on host {} " + "(rollback, unlock and close will be tried for safety): {}".format(self.equipment_access.fqdn, e)) self.configuration.rollback() self.configuration.unlock() self.close() - - log.info( - "Execute configuration on host {}. Result message: '{}'".format( - self.equipment_access.fqdn, result_message)) - - return result_message + raise Exception def ensure_privilege_level(self, privilege_level=None): """ - Ensure privilege level. - This function only verify is the current user is a super-user, otherwise raises an exception + Ensure privilege level verifying if the current user is super-user. + + :returns: + Returns True if success, otherwise, raise an exception (it will NOT return a false result) """ log.info("Trying to ensure privilege level for user {} on host {} ... ".format( self.equipment_access.user, self.equipment_access.fqdn)) - # Note about StartShell timeout: duration of time in seconds must wait for the expected result - ss = StartShell(self.remote_conn, timeout=2) - ss.open() - output = ss.run('cli -c "show cli authorization"') - - # output is a tuple [bool, string], example: - # (False, u'cli -c "show cli authorization"\r\r\nCurrent user: \'root \' class \'super-user\ ....) - # This string will be parsed to get the user class: - result = output[1].split('\n') # get the target part and split it by \n - current_user_class = result[1].split("'")[3] # get the target part, split again by ' and get the target part - if current_user_class != 'super-user': - log.error("{} {}".format("User {} has no privileges", self.equipment_access.user, self.equipment_access.fqdn)) + try: + # timeout: duration of time in seconds must wait for the expected result + ss = StartShell(self.remote_conn, timeout=2) + ss.open() + output = ss.run('cli -c "show cli authorization"') + + # output is a tuple [bool, string], example: + # (False, u'cli -c "show cli authorization"\r\r\nCurrent user: \'root \' class \'super-user\ ....) + # This string will be parsed to get the user class: + + # get the target part and split it by \n + result = output[1].split('\n') + + # get the target part; split by '; and get the target part + current_user_class = result[1].split("'")[3] + + if current_user_class != 'super-user': + log.error("Couldn't validate user {} privileges on host {}. " + "User class is '{}' and need to be 'super-user'. " + "(close connection will be executed for safety)" + .format(self.equipment_access.user, self.equipment_access.fqdn, current_user_class)) + self.close() + raise Exception + else: + log.info("The privilege for user {} ('super-user') was satisfied on host {}!".format( + self.equipment_access.user, self.equipment_access.fqdn)) + return True + + except Exception as e: + log.error("An exception error occurred during the user privilege verification on host {} " + "(close connection will be executed for safety): {}".format(self.equipment_access.fqdn, e)) self.close() - return False - else: - log.info("The privilege for user {}, on host {}, was satisfied! ".format( - self.equipment_access.user, self.equipment_access.fqdn)) - return True + raise Exception - def plugin_try_lock(self): + def __try_lock(self): + + """ + Try to lock equipment. + + :returns: + Returns True if success, otherwise, raise an exception. That means will NOT return a false result. + """ for x in range(self.quantity_of_times_to_try_lock): try: self.configuration.lock() + log.info("Host {} was locked successfully!".format(self.equipment_access.fqdn)) return True except (LockError, RpcError, Exception), e: + + count = x + 1 # Keep looping ... log.warning( - "Configuration still could not be locked on host {}. " - "Automatic try in {} seconds - {}/{} {}".format( + "Configuration still could not be locked on host {}. Automatic try in {} seconds - {}/{} {}".format( self.equipment_access.fqdn, self.seconds_to_wait_to_try_lock, - x+1, + count, self.quantity_of_times_to_try_lock, e)) - time.sleep(self.seconds_to_wait_to_try_lock) - return False + if count == self.quantity_of_times_to_try_lock: + log.error("Host {} couldn't be locked: {}".format(self.equipment_access.fqdn, e)) + raise Exception + else: + time.sleep(self.seconds_to_wait_to_try_lock) From 7053b28eb4c2f06c2a21c39e7c3a0da2bcd2b6c6 Mon Sep 17 00:00:00 2001 From: malinoski Date: Thu, 22 Oct 2020 15:59:45 -0300 Subject: [PATCH 27/36] Was developed for plugin junos a new function used to find, validade and build the configuration file path, including better general error messages. Test cases were developed to cover basic asserts. --- networkapi/plugins/Juniper/JUNOS/plugin.py | 87 +++++++++++++++++++--- networkapi/plugins/Juniper/JUNOS/tests.py | 33 ++++++++ 2 files changed, 109 insertions(+), 11 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index 21fcaa62e..b38d891a0 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -16,10 +16,14 @@ import logging import time +import os.path from exceptions import IOError from networkapi.plugins.base import BasePlugin from networkapi.plugins import exceptions from networkapi.equipamento.models import EquipamentoAcesso +from networkapi.system.facade import get_value +from networkapi.system.exceptions import VariableDoesNotExistException +from django.db.utils import DatabaseError from jnpr.junos import Device from jnpr.junos.utils.config import Config from jnpr.junos.utils.start_shell import StartShell @@ -39,6 +43,8 @@ class JUNOS(BasePlugin): configuration = None quantity_of_times_to_try_lock = 3 seconds_to_wait_to_try_lock = 10 + alternative_variable_base_path_list = ['path_to_tftpboot'] + alternative_static_base_path_list = ['/mnt/scripts/tftpboot/'] def __init__(self, **kwargs): super(JUNOS, self).__init__(connect_port=830, **kwargs) @@ -63,7 +69,7 @@ def connect(self): self.equipment_access = EquipamentoAcesso.search( None, self.equipment, 'ssh').uniqueResult() except Exception: - log.error("Access type {} not found for equipment {}.".format('ssh', self.equipment.nome)) + log.error("Unknown error while accessing equipment {} in database.".format(self.equipment.nome)) raise exceptions.InvalidEquipmentAccessException() log.info("Trying to connect on host {} ... ".format(self.equipment_access.fqdn)) @@ -86,7 +92,7 @@ def connect(self): raise ConnectError except Exception, e: - log.error("Error connecting to host {}: {}".format(self.equipment_access.fqdn, e)) + log.error("Unknown error while connecting to host {}: {}".format(self.equipment_access.fqdn, e)) raise Exception def close(self): @@ -110,7 +116,7 @@ def close(self): raise ConnectClosedError except Exception, e: - log.error("Unexpected error at closing connection on host {}: {}".format(self.equipment_access.fqdn, e)) + log.error("Unknown error while closing connection on host {}: {}".format(self.equipment_access.fqdn, e)) raise Exception def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): @@ -129,19 +135,24 @@ def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): log.info("Trying to load file configuration for host {} ...".format(self.equipment_access.fqdn)) + # 'filename' was defined in super class, but in plugin junos the 'file_path' will be used instead + file_path = filename + file_path = self.check_configuration_file_exists(file_path) + try: - command_file = open(filename, "r") + + command_file = open(file_path, "r") command = command_file.read() - log.info("Load configuration from file {} successfully!".format(filename)) + log.info("Load configuration from file {} successfully!".format(file_path)) return self.exec_command(command) except IOError, e: - log.error("File not found {}: {}".format(filename, e)) + log.error("File not found {}: {}".format(file_path, e)) self.close() raise IOError except Exception, e: - log.error("Unexpected error occurred {}: {}".format(filename, e)) + log.error("Unknown error while accessing configuration file {}: {}".format(file_path, e)) self.close() raise Exception @@ -209,7 +220,7 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege raise RpcError except Exception as e: - log.error("An exception error occurred during configuration execution on host {} " + log.error("Unknown error while executing configuration on host {} " "(rollback, unlock and close will be tried for safety): {}".format(self.equipment_access.fqdn, e)) self.configuration.rollback() self.configuration.unlock() @@ -257,7 +268,7 @@ def ensure_privilege_level(self, privilege_level=None): return True except Exception as e: - log.error("An exception error occurred during the user privilege verification on host {} " + log.error("Unknown error while verifying user privilege on host {} " "(close connection will be executed for safety): {}".format(self.equipment_access.fqdn, e)) self.close() raise Exception @@ -271,6 +282,8 @@ def __try_lock(self): Returns True if success, otherwise, raise an exception. That means will NOT return a false result. """ + log.info("Trying to lock host {} ...".format(self.equipment_access.fqdn)) + for x in range(self.quantity_of_times_to_try_lock): try: self.configuration.lock() @@ -281,7 +294,7 @@ def __try_lock(self): count = x + 1 # Keep looping ... log.warning( - "Configuration still could not be locked on host {}. Automatic try in {} seconds - {}/{} {}".format( + "Host {} could not be locked. Automatic try in {} seconds - {}/{} {}".format( self.equipment_access.fqdn, self.seconds_to_wait_to_try_lock, count, @@ -289,7 +302,59 @@ def __try_lock(self): e)) if count == self.quantity_of_times_to_try_lock: - log.error("Host {} couldn't be locked: {}".format(self.equipment_access.fqdn, e)) + log.error("An error occurred while trying to lock host {}".format(self.equipment_access.fqdn)) raise Exception else: time.sleep(self.seconds_to_wait_to_try_lock) + + def check_configuration_file_exists(self, file_path): + + """ + This function try to find and build (if necessary) the configuration file path. The priorities are: + (1) build the full path from system variable base and relative file path ('file_path'); or + (2) build the full path from static variable base and relative file path ('file_path'); or + (3) return the relative path it self ('file_path') + + :param str file_path: Relative path, examples: + 'networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt' or + 'networkapi/generated_config/interface/int-d_24823_config_ROR9BX3ATQG93TALJAMO2G' + + :return: Return a valid configuration file path string. Ex.: + 'networkapi/plugins/Juniper/JUNOS/samples/sample_command.txt' or + '/mnt/scripts/tftpboot/networkapi/generated_config/interface/int-d_24823_config_ROR9BX3ATQG93TALJAMO2G' + """ + + log.info("Checking configuration file exist: {}".format(file_path)) + + # Check in system variables + for variable in self.alternative_variable_base_path_list: + try: + base_path = get_value(variable) + if base_path != "": + result_path = base_path + file_path + if os.path.isfile(result_path): + log.info("Configuration file {} was found by system variable {}!".format(result_path, variable)) + return result_path + except (DatabaseError, VariableDoesNotExistException): + # DatabaseError means that variable table do not exist + pass + except Exception, e: + log.warning("Unknown error while calling networkapi.system.facade.get_value({}): {} {} ".format( + variable, e.__class__, e)) + + # Check possible static variables + for static_path in self.alternative_static_base_path_list: + result_path = static_path + file_path + if os.path.isfile(result_path): + log.info("Configuration file {} was found by static variable {}!".format(result_path, static_path)) + return result_path + + # Check if relative path is valid (for dev tests) + if os.path.isfile(file_path): + log.info("Configuration file {} was found by relative path".format(file_path)) + return file_path + + log.error("An error occurred while finding configuration file in: " + "relative path ('{}') or system variables ({}) or static paths ({})" + .format(file_path, self.alternative_variable_base_path_list, self.alternative_static_base_path_list)) + raise Exception diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index 837c0c9fe..2b2e3e9a7 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -1,6 +1,7 @@ from networkapi.test.test_case import NetworkApiTestCase from networkapi.plugins.base import BasePlugin from networkapi.plugins.Juniper.JUNOS.plugin import JUNOS +import mock from mock import patch, MagicMock @@ -116,3 +117,35 @@ def test_call_copyScriptFileToConfig(self, mock_junos_plugin): def test_call_ensure_privilege_level(self, mock_junos_plugin): mock_junos_plugin.ensure_privilege_level() mock_junos_plugin.ensure_privilege_level.assert_called_with() + + @patch('os.path.isfile') + @patch('networkapi.plugins.Juniper.JUNOS.plugin.get_value') + def test_check_configuration_file_exists_from_system_variable(self, mock_get_value, mock_is_file): + plugin = JUNOS(equipment_access=self.mock_equipment_access) + mock_get_value.return_value = "/any_base_variable_path/" + mock_is_file.return_value = True + + # isfile.return_value = True + result = plugin.check_configuration_file_exists("any_configuration_path_with_file_name") + self.assertEqual(result, "/any_base_variable_path/any_configuration_path_with_file_name") + + @patch('os.path.isfile') + def test_check_configuration_file_exists_from_static_variable(self, mock_is_file): + plugin = JUNOS(equipment_access=self.mock_equipment_access) + mock_is_file.return_value = True + first_alternative_static_base_path = plugin.alternative_static_base_path_list[0] + + result = plugin.check_configuration_file_exists("any_configuration_path_with_file_name") + self.assertEqual(result, first_alternative_static_base_path+"any_configuration_path_with_file_name") + + @patch('os.path.isfile') + def test_check_configuration_file_exists_from_relative_path(self, mock_is_file): + plugin = JUNOS(equipment_access=self.mock_equipment_access) + + # The function is_file(...), inside check_configuration_file_exists, is used two times. + # The proposed test need that function is_file(...) returns two different values at different places. + # Returning True at first and False at second time, where the principal test is executed + mock_is_file.side_effect = [False, True] + + result = plugin.check_configuration_file_exists("any_configuration_path_with_file_name") + self.assertEqual(result, "any_configuration_path_with_file_name") # Must be the same From 0c0e5ce9e79214ae52ff568349eb4c12c19b7cdb Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Thu, 22 Oct 2020 16:52:08 -0300 Subject: [PATCH 28/36] create update methods to ASNEquiment --- networkapi/api_asn/models.py | 26 +++++-- networkapi/api_asn/v4/facade.py | 117 ++++++++++++++++++++++++-------- networkapi/api_asn/v4/views.py | 35 ++++++++++ 3 files changed, 144 insertions(+), 34 deletions(-) diff --git a/networkapi/api_asn/models.py b/networkapi/api_asn/models.py index 036f21065..7be0e5493 100644 --- a/networkapi/api_asn/models.py +++ b/networkapi/api_asn/models.py @@ -148,12 +148,13 @@ def get_by_pk(cls, ids=None, asn=None, equipment=None): :raise OperationalError: Lock wait timeout exceeded """ try: + logging.info("get asn_equipment by id, asn or equipment") if ids: - return AsnEquipment.objects.get(id=ids) + return AsnEquipment.objects.get(id=int(ids)) elif asn: - return AsnEquipment.objects.get(asn__id=int(asn)) + return AsnEquipment.objects.filter(asn=int(asn)) elif equipment: - return AsnEquipment.objects.get(equipment__id=equipment) + return AsnEquipment.objects.filter(equipment__id=int(equipment)) return AsnEquipment.objects.all() @@ -163,9 +164,10 @@ def get_by_pk(cls, ids=None, asn=None, equipment=None): except OperationalError: cls.log.error(u'Lock wait timeout exceeded.') raise OperationalError() - except Exception: - cls.log.error(u'Failure to search the AS.') - raise exceptions.AsnEquipmentError(u'Failure to search the AS.') + except Exception as e: + cls.log.error(u'Failure to search the ASNEquipment. E: %s' % e) + raise exceptions.AsnEquipmentError( + u'Failure to search the ASNEquipment. E: %s' % e) def create_v4(self, as_equipment): """Create AsnEquipment relationship.""" @@ -184,3 +186,15 @@ def delete_v4(self): """Delete AsnEquipment relationship.""" super(AsnEquipment, self).delete() + + def update_v4(self, asn_equipment): + """Update ASNEquipment """ + + equipment = get_model('equipamento', 'Equipamento') + + self.equipment = equipment().get_by_pk( + asn_equipment.get('equipment')[0]) + self.asn = Asn().get_by_pk(asn_equipment.get('asn')) + self.save() + + return self diff --git a/networkapi/api_asn/v4/facade.py b/networkapi/api_asn/v4/facade.py index a5cdee753..c66808817 100644 --- a/networkapi/api_asn/v4/facade.py +++ b/networkapi/api_asn/v4/facade.py @@ -39,7 +39,7 @@ def get_as_by_id(as_id): try: as_ = Asn.get_by_pk(id=as_id) - except AsnNotFoundError, e: + except AsnNotFoundError as e: raise exceptions.AsnDoesNotExistException(str(e)) return as_ @@ -57,9 +57,9 @@ def get_as_by_ids(autonomous_systems_ids): try: as_ = get_as_by_id(as_id).id as_ids.append(as_) - except exceptions.AsnDoesNotExistException, e: + except exceptions.AsnDoesNotExistException as e: raise ObjectDoesNotExistException(str(e)) - except Exception, e: + except Exception as e: raise NetworkAPIException(str(e)) as_s = Asn.objects.filter(id__in=as_ids) @@ -73,13 +73,13 @@ def update_as(as_): try: as_obj = get_as_by_id(as_.get('id')) as_obj.update_v4(as_) - except AsnErrorV4, e: + except AsnErrorV4 as e: raise ValidationAPIException(str(e)) - except ValidationAPIException, e: + except ValidationAPIException as e: raise ValidationAPIException(str(e)) - except exceptions.AsnDoesNotExistException, e: + except exceptions.AsnDoesNotExistException as e: raise ObjectDoesNotExistException(str(e)) - except Exception, e: + except Exception as e: raise NetworkAPIException(str(e)) return as_obj @@ -91,11 +91,11 @@ def create_as(as_): try: as_obj = Asn() as_obj.create_v4(as_) - except AsnErrorV4, e: + except AsnErrorV4 as e: raise ValidationAPIException(str(e)) - except ValidationAPIException, e: + except ValidationAPIException as e: raise ValidationAPIException(str(e)) - except Exception, e: + except Exception as e: raise NetworkAPIException(str(e)) return as_obj @@ -108,13 +108,13 @@ def delete_as(as_ids): try: as_obj = get_as_by_id(as_id) as_obj.delete_v4() - except exceptions.AsnDoesNotExistException, e: + except exceptions.AsnDoesNotExistException as e: raise ObjectDoesNotExistException(str(e)) - except exceptions.AsnAssociatedToEquipmentError, e: + except exceptions.AsnAssociatedToEquipmentError as e: raise ValidationAPIException(str(e)) - except AsnError, e: + except AsnError as e: raise NetworkAPIException(str(e)) - except Exception, e: + except Exception as e: raise NetworkAPIException(str(e)) @@ -145,7 +145,7 @@ def get_as_equipment_by_id(as_equipment_id=None): for asn in as_equipment_id: as_equipment = AsnEquipment.get_by_pk(ids=asn) as_equipment_list.append(as_equipment) - except AsnNotFoundError, e: + except AsnNotFoundError as e: raise exceptions.AsnDoesNotExistException(str(e)) return as_equipment_list @@ -160,15 +160,14 @@ def get_as_equipment_by_asn(asn_id=None): """ try: - as_equipment_list = list() + as_equipment = list() for asn in asn_id: - as_equipment = AsnEquipment.get_by_pk(asn=asn) - as_equipment_list.append(as_equipment) + as_equipment += AsnEquipment.get_by_pk(asn=asn) - except AsnNotFoundError, e: + except AsnNotFoundError as e: raise exceptions.AsnDoesNotExistException(str(e)) - return as_equipment_list + return as_equipment def get_as_equipment_by_equip(equipment_id=None): @@ -180,15 +179,14 @@ def get_as_equipment_by_equip(equipment_id=None): """ try: - as_equipment_list = list() + as_equipment = list() for equip in equipment_id: - as_equipment = AsnEquipment.get_by_pk(equipment=equip) - as_equipment_list.append(as_equipment) + as_equipment += AsnEquipment.get_by_pk(equipment=equip) - except AsnNotFoundError, e: + except AsnNotFoundError as e: raise exceptions.AsnDoesNotExistException(str(e)) - return as_equipment_list + return as_equipment def create_asn_equipment(asn_equipment): @@ -205,11 +203,74 @@ def create_asn_equipment(asn_equipment): as_obj.create_v4(obj) asn_equipment_list.append({'id': as_obj.id}) - except AsnErrorV4, e: + except AsnErrorV4 as e: raise ValidationAPIException(str(e)) - except ValidationAPIException, e: + except ValidationAPIException as e: raise ValidationAPIException(str(e)) - except Exception, e: + except Exception as e: raise NetworkAPIException(str(e)) return asn_equipment_list + + +def delete_as_equipment(as_ids): + """Delete ASNEquipment.""" + + for as_id in as_ids: + try: + as_obj = get_as_equipment_by_id(as_id)[0] + log.debug(as_obj) + as_obj.delete_v4() + except exceptions.AsnDoesNotExistException as e: + raise ObjectDoesNotExistException(str(e)) + except exceptions.AsnAssociatedToEquipmentError as e: + raise ValidationAPIException(str(e)) + except AsnError as e: + raise NetworkAPIException(str(e)) + except Exception as e: + raise NetworkAPIException(str(e)) + + +def update_asn_equipment(as_): + """Update AS.""" + + try: + as_obj = AsnEquipment() + asn_equipment = as_obj.get_by_pk(ids=as_.get('id')) + asn_equipment.update_v4(as_) + except AsnErrorV4 as e: + raise ValidationAPIException(str(e)) + except ValidationAPIException as e: + raise ValidationAPIException(str(e)) + except exceptions.AsnDoesNotExistException as e: + raise ObjectDoesNotExistException(str(e)) + except Exception as e: + raise NetworkAPIException(str(e)) + + return as_obj + + +def update_asn_equipment_by_asn(asn_id, as_): + """Update AS. Return new asn_equipments new ids""" + + try: + as_obj = AsnEquipment() + asn_equipment = as_obj.get_by_pk(asn=asn_id) + log.debug(asn_equipment) + log.debug(type(asn_equipment)) + for obj in asn_equipment: + obj.delete_v4() + + asn_equipment_obj = create_asn_equipment(as_) + + except AsnErrorV4 as e: + raise ValidationAPIException(str(e)) + except ValidationAPIException as e: + raise ValidationAPIException(str(e)) + except exceptions.AsnDoesNotExistException as e: + raise ObjectDoesNotExistException(str(e)) + except Exception as e: + raise NetworkAPIException(str(e)) + + return asn_equipment_obj + diff --git a/networkapi/api_asn/v4/views.py b/networkapi/api_asn/v4/views.py index 486303fac..3ae3d47bd 100644 --- a/networkapi/api_asn/v4/views.py +++ b/networkapi/api_asn/v4/views.py @@ -173,3 +173,38 @@ def post(self, request, *args, **kwargs): response = facade.create_asn_equipment(as_) return Response(response, status=status.HTTP_201_CREATED) + + @logs_method_apiview + @permission_classes_apiview((IsAuthenticated, Write)) + @commit_on_success + def delete(self, request, *args, **kwargs): + """Delete AS.""" + + obj_ids = kwargs['obj_ids'].split(';') + log.debug(obj_ids) + + facade.delete_as_equipment(obj_ids) + + return Response({}, status=status.HTTP_200_OK) + + @logs_method_apiview + # @raise_json_validate('as_put_v4') + @permission_classes_apiview((IsAuthenticated, Write)) + @commit_on_success + def put(self, request, *args, **kwargs): + """Update AS.""" + + asn_equipment = request.DATA + # json_validate(SPECS.get('as_put_v4')).validate(as_s) + response = list() + + if not kwargs.get('asn_ids'): + for as_ in asn_equipment['asn_equipment']: + as_obj = facade.update_asn_equipment(as_) + response.append({'id': as_obj.id}) + else: + for as_ in asn_equipment['asn_equipment']: + response = facade.update_asn_equipment_by_asn( + kwargs.get('asn_ids'), as_) + + return Response(response, status=status.HTTP_200_OK) From 3d08a918e14485532c05f07771a49de7fdef1497 Mon Sep 17 00:00:00 2001 From: malinoski Date: Fri, 23 Oct 2020 13:51:34 -0300 Subject: [PATCH 29/36] Created a script python witch help to test Junos commands from terminal --- .../JUNOS/samples/sample_command_line.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py diff --git a/networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py b/networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py new file mode 100644 index 000000000..f411a7bd6 --- /dev/null +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py @@ -0,0 +1,60 @@ +""" +How to use: +python sample_command_line.py -device 'HOST' -user 'SSH_USER_NAME' -password 'SSH_USER_PASSWORD' -command 'COMMAND' +""" + +from lxml import etree +from jnpr.junos import Device +from jnpr.junos.utils.config import Config +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('-device', help='Input host', type=str) +parser.add_argument('-user', help='Input user name', type=str) +parser.add_argument('-password', help='Input password', type=str) +parser.add_argument('-command', help='Input command', type=str) +args, unknown = parser.parse_known_args() + +device = args.device +user = args.user +password = args.password +command = args.command + +try: + + dev = Device(host=device, user=user, password=password, gather_facts=False) + open_result = dev.open() + print("Open connection ... {}".format(open_result.connected)) + + conf = Config(dev) + print("Load config ...") + + lock_response = conf.lock() + print("Locking config ... {}".format(lock_response)) + + rollback_response = conf.rollback() + print("Rollback config ... {}".format(rollback_response)) + + load_result = conf.load(command, format='set') + load_result_tostring = etree.tostring(load_result, encoding='unicode', pretty_print=True) + print("Load command ... \n{}".format(load_result_tostring)) + + commit_check_result = conf.commit_check() + print("Check result ... {}".format(commit_check_result)) + + commit_result = conf.commit() + print("Commit ... {}".format(commit_result)) + + unlock_response = conf.unlock() + print("Unlocking config ... {}".format(unlock_response)) + + close_response = dev.close() + print("Close connection ... {}".format("Success" if not open_result.connected else "Failed")) + + print ("DONE") + +except Exception, e: + + print(e) + close_response = dev.close() + print("Closed connection? {}".format(not open_result.connected)) From 2881b11d4cbc80bc0899d81edf119b8a626dd624 Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Fri, 23 Oct 2020 15:39:36 -0300 Subject: [PATCH 30/36] fix get method --- networkapi/api_asn/v4/facade.py | 55 ++++++++++++++++++++++----------- networkapi/api_asn/v4/urls.py | 4 +-- networkapi/api_asn/v4/views.py | 11 +++++-- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/networkapi/api_asn/v4/facade.py b/networkapi/api_asn/v4/facade.py index c66808817..a124825a0 100644 --- a/networkapi/api_asn/v4/facade.py +++ b/networkapi/api_asn/v4/facade.py @@ -213,26 +213,44 @@ def create_asn_equipment(asn_equipment): return asn_equipment_list -def delete_as_equipment(as_ids): +def delete_asn_equipment(as_ids): """Delete ASNEquipment.""" - for as_id in as_ids: - try: - as_obj = get_as_equipment_by_id(as_id)[0] - log.debug(as_obj) - as_obj.delete_v4() - except exceptions.AsnDoesNotExistException as e: - raise ObjectDoesNotExistException(str(e)) - except exceptions.AsnAssociatedToEquipmentError as e: - raise ValidationAPIException(str(e)) - except AsnError as e: - raise NetworkAPIException(str(e)) - except Exception as e: - raise NetworkAPIException(str(e)) + try: + asn_equipment = AsnEquipment() + obj = asn_equipment.get_by_pk(ids=as_ids) + obj.delete_v4() + except exceptions.AsnDoesNotExistException as e: + raise ObjectDoesNotExistException(str(e)) + except exceptions.AsnAssociatedToEquipmentError as e: + raise ValidationAPIException(str(e)) + except AsnError as e: + raise NetworkAPIException(str(e)) + except Exception as e: + raise NetworkAPIException(str(e)) + + +def delete_asn_equipment_by_asn(asn_id): + """Delete ASNEquipment.""" + + try: + asn_obj = AsnEquipment() + asn_equipment = asn_obj.get_by_pk(asn=asn_id) + for obj in asn_equipment: + obj.delete_v4() + + except exceptions.AsnDoesNotExistException as e: + raise ObjectDoesNotExistException(str(e)) + except exceptions.AsnAssociatedToEquipmentError as e: + raise ValidationAPIException(str(e)) + except AsnError as e: + raise NetworkAPIException(str(e)) + except Exception as e: + raise NetworkAPIException(str(e)) def update_asn_equipment(as_): - """Update AS.""" + """Update ASNEquipment.""" try: as_obj = AsnEquipment() @@ -251,13 +269,14 @@ def update_asn_equipment(as_): def update_asn_equipment_by_asn(asn_id, as_): - """Update AS. Return new asn_equipments new ids""" + """ + Update ASNEquipment. + Return new asn_equipments new ids + """ try: as_obj = AsnEquipment() asn_equipment = as_obj.get_by_pk(asn=asn_id) - log.debug(asn_equipment) - log.debug(type(asn_equipment)) for obj in asn_equipment: obj.delete_v4() diff --git a/networkapi/api_asn/v4/urls.py b/networkapi/api_asn/v4/urls.py index 860f5e939..a270ed294 100644 --- a/networkapi/api_asn/v4/urls.py +++ b/networkapi/api_asn/v4/urls.py @@ -6,12 +6,12 @@ urlpatterns = patterns( '', - url(r'^asnequipment/((?P[;\w]+)/)?$', - views.AsEquipmentDBView.as_view()), url(r'^asnequipment/asn/((?P[;\w]+)/)?$', views.AsEquipmentDBView.as_view()), url(r'^asnequipment/equipment/((?P[;\w]+)/)?$', views.AsEquipmentDBView.as_view()), + url(r'^asnequipment/((?P[;\w]+)/)?$', + views.AsEquipmentDBView.as_view()), url(r'^as/((?P[;\w]+)/)?$', views.AsDBView.as_view()), ) diff --git a/networkapi/api_asn/v4/views.py b/networkapi/api_asn/v4/views.py index 3ae3d47bd..838fee1a3 100644 --- a/networkapi/api_asn/v4/views.py +++ b/networkapi/api_asn/v4/views.py @@ -180,10 +180,15 @@ def post(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs): """Delete AS.""" - obj_ids = kwargs['obj_ids'].split(';') - log.debug(obj_ids) + if not kwargs.get('asn_ids'): - facade.delete_as_equipment(obj_ids) + obj_ids = kwargs['obj_ids'].split(';') + for obj in obj_ids: + facade.delete_asn_equipment(obj) + else: + obj_ids = kwargs['asn_ids'].split(';') + for obj in obj_ids: + facade.delete_asn_equipment_by_asn(obj) return Response({}, status=status.HTTP_200_OK) From 64fd7c34a4d7ed926e37ee587da508db1a4c4e1a Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Fri, 23 Oct 2020 15:55:12 -0300 Subject: [PATCH 31/36] fix update method --- networkapi/api_asn/v4/facade.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/networkapi/api_asn/v4/facade.py b/networkapi/api_asn/v4/facade.py index a124825a0..498ffdd71 100644 --- a/networkapi/api_asn/v4/facade.py +++ b/networkapi/api_asn/v4/facade.py @@ -254,6 +254,8 @@ def update_asn_equipment(as_): try: as_obj = AsnEquipment() + if not as_.get('id'): + raise Exception("The object do not have the id.") asn_equipment = as_obj.get_by_pk(ids=as_.get('id')) asn_equipment.update_v4(as_) except AsnErrorV4 as e: @@ -265,7 +267,7 @@ def update_asn_equipment(as_): except Exception as e: raise NetworkAPIException(str(e)) - return as_obj + return asn_equipment def update_asn_equipment_by_asn(asn_id, as_): From 25d71b64788a5af8f2a0c8a160711df8d6624b33 Mon Sep 17 00:00:00 2001 From: "laura.panzariello" Date: Sat, 24 Oct 2020 18:24:02 -0300 Subject: [PATCH 32/36] create asn-rack relationship when the environments are allocated --- networkapi/api_asn/models.py | 22 ++++ networkapi/api_asn/v4/facade.py | 15 +++ networkapi/api_rack/facade.py | 147 ++++++++++++++++-------- networkapi/api_rack/rackenvironments.py | 95 ++++++++++----- 4 files changed, 200 insertions(+), 79 deletions(-) diff --git a/networkapi/api_asn/models.py b/networkapi/api_asn/models.py index 7be0e5493..e10bd7a49 100644 --- a/networkapi/api_asn/models.py +++ b/networkapi/api_asn/models.py @@ -61,6 +61,28 @@ def get_by_pk(cls, id): cls.log.error(u'Failure to search the ASN.') raise exceptions.AsnError(u'Failure to search the ASN.') + def get_by_asn(cls, asn): + """Get AS by id. + + :return: AS. + + :raise AsnNotFoundError: As not registered. + :raise AsnError: Failed to search for the As. + :raise OperationalError: Lock wait timeout exceeded + """ + try: + return Asn.objects.get(asn=asn) + except ObjectDoesNotExist: + cls.log.error(u'ASN not found. pk {}'.format(id)) + raise exceptions.AsnNotFoundError(id) + except OperationalError: + cls.log.error(u'Lock wait timeout exceeded.') + raise OperationalError() + except Exception: + cls.log.error(u'Failure to search the ASN.') + raise exceptions.AsnError(u'Failure to search the ASN.') + + def create_v4(self, as_map): """Create ASN.""" diff --git a/networkapi/api_asn/v4/facade.py b/networkapi/api_asn/v4/facade.py index 498ffdd71..c46799978 100644 --- a/networkapi/api_asn/v4/facade.py +++ b/networkapi/api_asn/v4/facade.py @@ -45,6 +45,21 @@ def get_as_by_id(as_id): return as_ +def get_as_by_asn(asn_): + """Return an AS by id. + + Args: + asn: ASN + """ + + try: + as_ = Asn.get_by_asn(asn_) + except AsnNotFoundError as e: + raise exceptions.AsnDoesNotExistException(str(e)) + + return as_ + + def get_as_by_ids(autonomous_systems_ids): """Return AS list by ids. diff --git a/networkapi/api_rack/facade.py b/networkapi/api_rack/facade.py index b7a603e1e..083e22425 100644 --- a/networkapi/api_rack/facade.py +++ b/networkapi/api_rack/facade.py @@ -36,7 +36,6 @@ from networkapi.api_rack import serializers as rack_serializers from networkapi.api_rack import exceptions from networkapi.api_rack import provision -from networkapi.api_rack import autoprovision from networkapi.system import exceptions as var_exceptions from networkapi.system.facade import get_value as get_variable from networkapi.api_rest.exceptions import ValidationAPIException, ObjectDoesNotExistException, \ @@ -140,7 +139,6 @@ def get_fabric(idt=None, name=None, id_dc=None): else: fabric = fabric_obj.get_dcrooms() - for i in fabric: fabric_list.append(rack_serializers.DCRoomSerializer(i).data) @@ -154,7 +152,7 @@ def update_fabric_config(fabric_id, fabric_dict): try: fabriconfig = ast.literal_eval(fabric.config) - except: + except Exception: fabriconfig = dict() fabric_dict = fabric_dict.get("config") @@ -195,10 +193,12 @@ def save_rack_dc(rack_dict): rack.id_sw1 = Equipamento().get_by_pk(rack_dict.get('id_sw1')) rack.id_sw2 = Equipamento().get_by_pk(rack_dict.get('id_sw2')) rack.id_sw3 = Equipamento().get_by_pk(rack_dict.get('id_ilo')) - rack.dcroom = DatacenterRooms().get_dcrooms(idt=rack_dict.get('fabric_id')) if rack_dict.get('fabric_id') else None + rack.dcroom = DatacenterRooms().get_dcrooms( + idt=rack_dict.get('fabric_id')) if rack_dict.get('fabric_id') else None if not rack.nome: - raise exceptions.InvalidInputException("O nome do Rack não foi informado.") + raise exceptions.InvalidInputException( + "O nome do Rack não foi informado.") rack.save_rack() return rack @@ -218,8 +218,8 @@ def update_rack(rack_id, rack): rack_obj.id_sw1 = Equipamento().get_by_pk(int(rack.get("id_sw1"))) rack_obj.id_sw2 = Equipamento().get_by_pk(int(rack.get("id_sw2"))) rack_obj.id_ilo = Equipamento().get_by_pk(int(rack.get("id_ilo"))) - rack_obj.dcroom = DatacenterRooms().get_dcrooms(idt=rack.get('fabric_id')) if rack.get('fabric_id') \ - else None + rack_obj.dcroom = DatacenterRooms().get_dcrooms( + idt=rack.get('fabric_id')) if rack.get('fabric_id') else None rack_obj.save() @@ -242,13 +242,16 @@ def get_rack(fabric_id=None, rack_id=None): try: if fabric_id: rack = rack_obj.get_rack(dcroom_id=fabric_id) - rack_list = rack_serializers.RackSerializer(rack, many=True).data if rack else list() + rack_list = rack_serializers.RackSerializer( + rack, many=True).data if rack else list() elif rack_id: rack = rack_obj.get_rack(idt=rack_id) - rack_list = [rack_serializers.RackSerializer(rack).data] if rack else list() + rack_list = [rack_serializers.RackSerializer( + rack).data] if rack else list() else: rack = rack_obj.get_rack() - rack_list = rack_serializers.RackSerializer(rack, many=True).data if rack else list() + rack_list = rack_serializers.RackSerializer( + rack, many=True).data if rack else list() return rack_list @@ -327,9 +330,10 @@ def _buscar_ip(id_sw): for ip_equip in ips_equip: ip_sw = ip_equip.ip - if not ip_sw == None: + if not ip_sw is None: if regexp.search(ip_sw.networkipv4.vlan.ambiente.ambiente_logico.nome) is not None: - return str(ip_sw.oct1) + '.' + str(ip_sw.oct2) + '.' + str(ip_sw.oct3) + '.' + str(ip_sw.oct4) + return str(ip_sw.oct1) + '.' + str(ip_sw.oct2) + '.' \ + + str(ip_sw.oct3) + '.' + str(ip_sw.oct4) return "" @@ -368,7 +372,9 @@ def gerar_arquivo_config(ids): oob["modelo"] = rack.id_ilo.modelo.nome equips.append(oob) except: - raise Exception("Erro: Informações incompletas. Verifique o cadastro do Datacenter, do Fabric e do Rack") + raise Exception("Erro: Informações incompletas. " + "Verifique o cadastro do Datacenter, " + "do Fabric e do Rack") prefixspn = "SPN" prefixlf = "LF-" @@ -395,13 +401,15 @@ def gerar_arquivo_config(ids): pass except: raise Exception( - "Erro ao buscar o roteiro de configuracao ou as interfaces associadas ao equipamento: %s." - % equip.get("nome")) + "Erro ao buscar o roteiro de configuracao ou as interfaces " + "associadas ao equipamento: %s." % equip.get("nome")) try: - equip["roteiro"] = _buscar_roteiro(equip.get("id"), "CONFIGURACAO") + equip["roteiro"] = _buscar_roteiro(equip.get("id"), + "CONFIGURACAO") equip["ip_mngt"] = _buscar_ip(equip.get("id")) except: - raise Exception("Erro ao buscar os roteiros do equipamento: %s" % equip.get("nome")) + raise Exception("Erro ao buscar os roteiros do equipamento: " + "%s" % equip.get("nome")) # autoprovision.autoprovision_splf(rack, equips) # autoprovision.autoprovision_coreoob(rack, equips) @@ -448,7 +456,8 @@ def _create_spnlfenv(user, rack): for spn in range(spines): amb_log_name = "SPINE0" + str(spn+1) + "LEAF" try: - id_amb_log = models_env.AmbienteLogico().get_by_name(amb_log_name).id + id_amb_log = models_env.AmbienteLogico().get_by_name( + amb_log_name).id except: amb_log_dict = models_env.AmbienteLogico() amb_log_dict.nome = amb_log_name @@ -518,7 +527,8 @@ def _create_spnlfvlans(rack, user): base_rack = 1 vlan_base = env.min_num_vlan_1 vlan_number = int(vlan_base) + int(rack_number) + (spn-1)*base_rack - vlan_name = "VLAN_" + env.divisao_dc.nome + "_" + env.ambiente_logico.nome + "_" + rack.nome + vlan_name = "VLAN_" + env.divisao_dc.nome + "_" + env.ambiente_logico.nome \ + + "_" + rack.nome for net in env.configs: prefix = int(net.subnet_mask) @@ -788,7 +798,8 @@ def _create_lflf_envs(rack): for env in env_lf: config_subnet = [] for net in env.configs: - cidr = list(IPNetwork(net.network).subnet(int(net.subnet_mask)))[rack.numero] + cidr = list(IPNetwork(net.network).subnet(int( + net.subnet_mask)))[rack.numero] network = { 'network': str(cidr), 'ip_version': str(net.ip_version), @@ -907,9 +918,12 @@ def _create_oobvlans(rack, user): oct4, prefix = var.split('/') netmask = str(new_v4.netmask) mask1, mask2, mask3, mask4 = netmask.split('.') - network = dict(oct1=oct1, oct2=oct2, oct3=oct3, oct4=oct4, prefix=prefix, mask_oct1=mask1, mask_oct2=mask2, - mask_oct3=mask3, mask_oct4=mask4, cluster_unit=None, vlan=vlan.id, - network_type=config.id_network_type.id, environmentvip=None) + network = dict(oct1=oct1, oct2=oct2, oct3=oct3, + oct4=oct4, prefix=prefix, mask_oct1=mask1, + mask_oct2=mask2, mask_oct3=mask3, mask_oct4=mask4, + cluster_unit=None, vlan=vlan.id, + network_type=config.id_network_type.id, + environmentvip=None) log.debug("Network allocated: "+ str(network)) facade_redev4_v3.create_networkipv4(network, user) @@ -968,6 +982,8 @@ def allocate_env_vlan(user, rack_id): rack_env.allocate() + rack_env.create_asn_equipment() + return rack_env.rack @@ -980,6 +996,7 @@ def deallocate_env_vlan(user, rack_id): rack_env.rack_vlans_remove() rack_env.rack_environment_remove() + # rack_env.rack_asn_remove() rack_env.deallocate() @@ -992,59 +1009,87 @@ def api_foreman(rack): NETWORKAPI_FOREMAN_PASSWORD = get_variable("foreman_password") FOREMAN_HOSTS_ENVIRONMENT_ID = get_variable("foreman_hosts_environment_id") except ObjectDoesNotExist: - raise var_exceptions.VariableDoesNotExistException("Erro buscando as variáveis relativas ao Foreman.") + raise var_exceptions.VariableDoesNotExistException( + "Erro buscando as variáveis relativas ao Foreman.") - foreman = Foreman(NETWORKAPI_FOREMAN_URL, (NETWORKAPI_FOREMAN_USERNAME, NETWORKAPI_FOREMAN_PASSWORD), api_version=2) + foreman = Foreman(NETWORKAPI_FOREMAN_URL, + (NETWORKAPI_FOREMAN_USERNAME, + NETWORKAPI_FOREMAN_PASSWORD), + api_version=2) - # for each switch, check the switch ip against foreman know networks, finds foreman hostgroup + # for each switch, check the switch ip against foreman know networks, + # finds foreman hostgroup # based on model and brand and inserts the host in foreman # if host already exists, delete and recreate with new information - for [switch, mac] in [[rack.id_sw1, rack.mac_sw1], [rack.id_sw2, rack.mac_sw2], [rack.id_ilo, rack.mac_ilo]]: - # Get all foremand subnets and compare with the IP address of the switches until find it - if mac == None: - raise RackConfigError(None, rack.nome, ("Could not create entry for %s. There is no mac address." % - switch.nome)) + for [switch, mac] in [[rack.id_sw1, rack.mac_sw1], + [rack.id_sw2, rack.mac_sw2], + [rack.id_ilo, rack.mac_ilo]]: + # Get all foremand subnets and compare with the IP address + # of the switches until find it + if mac is None: + raise RackConfigError( + None, rack.nome, + ("Could not create entry for %s. " + "There is no mac address." % switch.nome)) ip = _buscar_ip(switch.id) - if ip == None: - raise RackConfigError(None, rack.nome, ("Could not create entry for %s. There is no management IP." % - switch.nome)) + if ip is None: + raise RackConfigError( + None, rack.nome, + ("Could not create entry for %s. " + "There is no management IP." % switch.nome)) switch_cadastrado = 0 for subnet in foreman.subnets.index()['results']: network = IPNetwork(ip + '/' + subnet['mask']).network - # check if switches ip network is the same as subnet['subnet']['network'] e subnet['subnet']['mask'] + # check if switches ip network is the same as subnet['subnet']['network'] + # e subnet['subnet']['mask'] if network.__str__() == subnet['network']: subnet_id = subnet['id'] - hosts = foreman.hosts.index(search = switch.nome)['results'] + hosts = foreman.hosts.index(search=switch.nome)['results'] if len(hosts) == 1: - foreman.hosts.destroy(id = hosts[0]['id']) + foreman.hosts.destroy(id=hosts[0]['id']) elif len(hosts) > 1: - raise RackConfigError(None, rack.nome, ("Could not create entry for %s. There are multiple entries " - "with the sam name." % switch.nome)) + raise RackConfigError( + None, rack.nome, + ("Could not create entry for %s. " + "There are multiple entries " + "with the sam name." % switch.nome)) # Lookup foreman hostgroup # By definition, hostgroup should be Marca+"_"+Modelo hostgroup_name = switch.modelo.marca.nome + "_" + switch.modelo.nome - hostgroups = foreman.hostgroups.index(search = hostgroup_name) + hostgroups = foreman.hostgroups.index(search=hostgroup_name) if len(hostgroups['results']) == 0: - raise RackConfigError(None, rack.nome, "Could not create entry for %s. Could not find hostgroup %s " - "in foreman." % (switch.nome, hostgroup_name)) - elif len(hostgroups['results'])>1: - raise RackConfigError(None, rack.nome, "Could not create entry for %s. Multiple hostgroups %s found" - " in Foreman." % (switch.nome, hostgroup_name)) + raise RackConfigError( + None, rack.nome, + "Could not create entry for %s. " + "Could not find hostgroup %s " + "in foreman." % (switch.nome, hostgroup_name)) + elif len(hostgroups['results']) > 1: + raise RackConfigError( + None, rack.nome, + "Could not create entry for %s. " + "Multiple hostgroups %s found" + " in Foreman." % (switch.nome, hostgroup_name)) else: hostgroup_id = hostgroups['results'][0]['id'] - foreman.hosts.create(host = {'name': switch.nome, 'ip': ip, 'mac': mac, - 'environment_id': FOREMAN_HOSTS_ENVIRONMENT_ID, - 'hostgroup_id': hostgroup_id, 'subnet_id': subnet_id, - 'build': 'true', 'overwrite': 'true'}) + foreman.hosts.create(host={'name': switch.nome, + 'ip': ip, + 'mac': mac, + 'environment_id': FOREMAN_HOSTS_ENVIRONMENT_ID, + 'hostgroup_id': hostgroup_id, + 'subnet_id': subnet_id, + 'build': 'true', + 'overwrite': 'true'}) switch_cadastrado = 1 if not switch_cadastrado: - raise RackConfigError(None, rack.nome, "Unknown error. Could not create entry for %s in foreman." % - switch.nome) + raise RackConfigError( + None, rack.nome, + "Unknown error. Could not create entry for " + "%s in foreman." % switch.nome) # ################################################### old diff --git a/networkapi/api_rack/rackenvironments.py b/networkapi/api_rack/rackenvironments.py index c3d1d0f95..a3a932e50 100644 --- a/networkapi/api_rack/rackenvironments.py +++ b/networkapi/api_rack/rackenvironments.py @@ -23,6 +23,8 @@ class RackEnvironment: def __init__(self, user, rack_id): self.rack = Rack().get_by_pk(rack_id) self.user = user + self.dcroom_config = self._get_config_file() + self.rack_asn = self._get_rack_asn() @staticmethod def save_environment(self, env): @@ -290,22 +292,7 @@ def prod_environment_save(self): id_grupo_l3 = grupo_l3_dict.id pass - if self.rack.dcroom.config: - fabricconfig = self.rack.dcroom.config - else: - log.debug("sem configuracoes do fabric %s" % str(self.rack.dcroom.id)) - fabricconfig = list() - - try: - fabricconfig = json.loads(fabricconfig) - except: - pass - - try: - fabricconfig = ast.literal_eval(fabricconfig) - log.debug("config -ast: %s" % str(fabricconfig)) - except: - pass + fabricconfig = self.dcroom_config environment = list() for env in prod_envs: @@ -376,18 +363,7 @@ def children_prod_environment_save(self): except Exception as e: raise Exception("Erro: %s" % e) - if self.rack.dcroom.config: - fabricconfig = self.rack.dcroom.config - else: - log.debug("No fabric configurations %s" % str(self.rack.dcroom.id)) - fabricconfig = list() - - try: - fabricconfig = json.loads(fabricconfig) - fabricconfig = ast.literal_eval(fabricconfig) - log.debug("config -ast: %s" % str(fabricconfig)) - except: - log.debug("Error loading fabric json.") + fabricconfig = self.dcroom_config environment = None father_id = env.id @@ -519,3 +495,66 @@ def rack_vlans_remove(self): vlan.delete_v3() log.debug("Vlans: %s. total: %s" % (vlans, len(vlans))) + + def rack_asn_remove(self): + from networkapi.api_asn.models import Asn + from networkapi.api_asn.v4 import facade + + if self.rack_asn: + asn = facade.get_as_by_asn(self.rack_asn) + asn_equipment = facade.get_as_equipment_by_asn([asn]) + log.debug(asn_equipment) + for obj in asn_equipment: + obj.delete_v4() + + Asn.delete_as([asn]) + + def create_asn_equipment(self): + from networkapi.api_asn.v4 import facade + + if self.rack_asn: + try: + asn_obj = dict(name=self.rack_asn, description=self.rack.nome) + asn = facade.create_as(asn_obj) + + obj = dict(asn=asn.id, equipment=[self.rack.id_sw1.id, + self.rack.id_sw2.id, + self.rack.id_ilo.id]) + facade.create_asn_equipment(obj) + except Exception as e: + log.debug("Error while trying to create the asn %s for rack %s. " + "E: %s" % (self.rack_asn, self.rack.nome, e)) + + def _get_rack_asn(self): + + if self.dcroom_config: + BGP = self.dcroom_config.get("BGP") + BASE_AS_LFS = int(BGP.get("leafs")) + rack_as = BASE_AS_LFS + self.rack.numero + + if rack_as: + return rack_as + else: + return None + + def _get_config_file(self): + + fabricconfig = self.rack.dcroom.config + + try: + fabricconfig = json.loads(fabricconfig) + log.debug("type -ast: %s" % str(type(fabricconfig))) + except: + pass + + try: + fabricconfig = ast.literal_eval(fabricconfig) + log.debug("config -ast: %s" % str(fabricconfig)) + except: + pass + + if fabricconfig: + return fabricconfig + else: + log.debug("sem configuracoes do fabric %s" % str(self.rack.dcroom.id)) + return list() From 6b70a357f9338b72b880ed7a65f8e51b6e61d84f Mon Sep 17 00:00:00 2001 From: malinoski Date: Mon, 26 Oct 2020 13:06:30 -0300 Subject: [PATCH 33/36] Included test cases to assert success or exception on ensure privilege method --- networkapi/plugins/Juniper/JUNOS/tests.py | 26 +++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index 2b2e3e9a7..f4d2563f5 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -113,10 +113,28 @@ def test_call_copyScriptFileToConfig(self, mock_junos_plugin): mock_junos_plugin.copyScriptFileToConfig("any file path") mock_junos_plugin.copyScriptFileToConfig.assert_called_with("any file path") - @patch('networkapi.plugins.Juniper.JUNOS.plugin.JUNOS', autospec=True) - def test_call_ensure_privilege_level(self, mock_junos_plugin): - mock_junos_plugin.ensure_privilege_level() - mock_junos_plugin.ensure_privilege_level.assert_called_with() + @patch('networkapi.plugins.Juniper.JUNOS.plugin.StartShell') + def test_call_ensure_privilege_level_success(self, mock_start_shell): + + """ + test_call_ensure_privilege_level_success + + Note: The shell run function expects an array as a return value, + and ensure_privilege_level() parse it to ensure the privilege. + """ + + mock_start_shell.return_value.run.return_value = [ + False, + u'cli -c "show cli authorization"\r\r\nCurrent user: \'root \' class \'super-user\'\r'] + + plugin = JUNOS(equipment_access=self.mock_equipment_access) + result = plugin.ensure_privilege_level() + self.assertTrue(result) + + def test_call_ensure_privilege_level_fail(self): + plugin = JUNOS(equipment_access=self.mock_equipment_access) + with self.assertRaises(Exception): + plugin.ensure_privilege_level() @patch('os.path.isfile') @patch('networkapi.plugins.Juniper.JUNOS.plugin.get_value') From a1ac883eb8f07327184683d0dbc6fe62164d7c76 Mon Sep 17 00:00:00 2001 From: malinoski Date: Tue, 27 Oct 2020 11:28:16 -0300 Subject: [PATCH 34/36] Junos plugin was updated with: ignore warning commands, in a programaticaly way; removed unsused function's parameters (inherited ones); refactored class attributes in proper way. The test cases and sample codes was updated accordly --- networkapi/plugins/Juniper/JUNOS/plugin.py | 20 ++++++++++--------- .../JUNOS/samples/sample_command_line.py | 2 +- networkapi/plugins/Juniper/JUNOS/tests.py | 12 +++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index b38d891a0..bbffabc99 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -40,11 +40,6 @@ class JUNOS(BasePlugin): - configuration = None - quantity_of_times_to_try_lock = 3 - seconds_to_wait_to_try_lock = 10 - alternative_variable_base_path_list = ['path_to_tftpboot'] - alternative_static_base_path_list = ['/mnt/scripts/tftpboot/'] def __init__(self, **kwargs): super(JUNOS, self).__init__(connect_port=830, **kwargs) @@ -54,6 +49,13 @@ def __init__(self, **kwargs): if 'seconds_to_wait_to_try_lock' in kwargs: self.seconds_to_wait_to_try_lock = kwargs.get('seconds_to_wait_to_try_lock') + self.configuration = None + self.quantity_of_times_to_try_lock = 3 + self.seconds_to_wait_to_try_lock = 10 + self.alternative_variable_base_path_list = ['path_to_tftpboot'] + self.alternative_static_base_path_list = ['/mnt/scripts/tftpboot/'] + self.ignore_warning_list = ['statement not found'] + def connect(self): """ @@ -119,7 +121,7 @@ def close(self): log.error("Unknown error while closing connection on host {}: {}".format(self.equipment_access.fqdn, e)) raise Exception - def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): + def copyScriptFileToConfig(self, filename): """ Receives the file path (usually in /mnt/scripts/tftpboot/networkapi/generated_config/interface/) @@ -156,7 +158,7 @@ def copyScriptFileToConfig(self, filename, use_vrf='', destination=''): self.close() raise Exception - def exec_command(self, command, success_regex='', invalid_regex=None, error_regex=None): + def exec_command(self, command): """ Execute a junos command 'set' in the equipment. @@ -175,7 +177,7 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege try: self.__try_lock() self.configuration.rollback() - self.configuration.load(command, format='set') + self.configuration.load(command, format='set', ignore_warning=self.ignore_warning_list) self.configuration.commit_check() self.configuration.commit() self.configuration.unlock() @@ -227,7 +229,7 @@ def exec_command(self, command, success_regex='', invalid_regex=None, error_rege self.close() raise Exception - def ensure_privilege_level(self, privilege_level=None): + def ensure_privilege_level(self): """ Ensure privilege level verifying if the current user is super-user. diff --git a/networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py b/networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py index f411a7bd6..79add3b3a 100644 --- a/networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py +++ b/networkapi/plugins/Juniper/JUNOS/samples/sample_command_line.py @@ -35,7 +35,7 @@ rollback_response = conf.rollback() print("Rollback config ... {}".format(rollback_response)) - load_result = conf.load(command, format='set') + load_result = conf.load(command, format='set', ignore_warning=['statement not found']) load_result_tostring = etree.tostring(load_result, encoding='unicode', pretty_print=True) print("Load command ... \n{}".format(load_result_tostring)) diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index f4d2563f5..aac040b60 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -64,7 +64,7 @@ def test_connect_success(self, mock_device): self.assertIsNotNone(plugin.configuration) self.assertEqual(connection_response, True) - @patch('jnpr.junos.utils.config.Config', autospec=True) + @patch('jnpr.junos.utils.config.Config') def test_exec_command_success(self, mock_config): """ @@ -82,11 +82,11 @@ def test_exec_command_success(self, mock_config): exec_command_response = plugin.exec_command("any command") # Assert - plugin.configuration.rollback.assert_called_once_with() - plugin.configuration.load.assert_called_once_with("any command", format='set') - plugin.configuration.commit_check.assert_called_once_with() - plugin.configuration.commit.assert_called_once_with() - plugin.configuration.unlock.assert_called_once_with() + plugin.configuration.rollback.assert_called_once() + plugin.configuration.load.assert_called_once() + plugin.configuration.commit_check.assert_called_once() + plugin.configuration.commit.assert_called_once() + plugin.configuration.unlock.assert_called_once() self.assertIsNotNone(exec_command_response) @patch('jnpr.junos.Device') From 5167356bcc8ebd6149d8f418a7f524900a2036e9 Mon Sep 17 00:00:00 2001 From: malinoski Date: Tue, 27 Oct 2020 15:01:19 -0300 Subject: [PATCH 35/36] Included junit test case for connect exception and plugin was updated accordingly --- networkapi/plugins/Juniper/JUNOS/plugin.py | 2 +- networkapi/plugins/Juniper/JUNOS/tests.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index bbffabc99..0e4d6125f 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -91,7 +91,7 @@ def connect(self): except ConnectError as e: log.error("Could not connect to Juniper host {}: {}".format(self.equipment_access.fqdn, e)) - raise ConnectError + raise ConnectError(e) except Exception, e: log.error("Unknown error while connecting to host {}: {}".format(self.equipment_access.fqdn, e)) diff --git a/networkapi/plugins/Juniper/JUNOS/tests.py b/networkapi/plugins/Juniper/JUNOS/tests.py index aac040b60..6dbf67910 100644 --- a/networkapi/plugins/Juniper/JUNOS/tests.py +++ b/networkapi/plugins/Juniper/JUNOS/tests.py @@ -1,8 +1,8 @@ from networkapi.test.test_case import NetworkApiTestCase from networkapi.plugins.base import BasePlugin from networkapi.plugins.Juniper.JUNOS.plugin import JUNOS -import mock from mock import patch, MagicMock +from jnpr.junos.exception import ConnectError, LockError class JunosPluginTest(NetworkApiTestCase): @@ -64,6 +64,16 @@ def test_connect_success(self, mock_device): self.assertIsNotNone(plugin.configuration) self.assertEqual(connection_response, True) + def test_connect_wrong_data_exception(self): + + """ + test_connect_wrong_data_exception + """ + + plugin = JUNOS(equipment_access=self.mock_equipment_access) + with self.assertRaises(ConnectError): + plugin.connect() + @patch('jnpr.junos.utils.config.Config') def test_exec_command_success(self, mock_config): From 6c6f86d32ea2c43fbe1d94b78c36b9d3b05fd419 Mon Sep 17 00:00:00 2001 From: malinoski Date: Tue, 27 Oct 2020 17:33:46 -0300 Subject: [PATCH 36/36] Junos plugin was reverted to use variablea like before, to follow the superclass architecture --- networkapi/plugins/Juniper/JUNOS/plugin.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/networkapi/plugins/Juniper/JUNOS/plugin.py b/networkapi/plugins/Juniper/JUNOS/plugin.py index 0e4d6125f..ec8a29b44 100644 --- a/networkapi/plugins/Juniper/JUNOS/plugin.py +++ b/networkapi/plugins/Juniper/JUNOS/plugin.py @@ -41,6 +41,13 @@ class JUNOS(BasePlugin): + configuration = None + quantity_of_times_to_try_lock = 3 + seconds_to_wait_to_try_lock = 10 + alternative_variable_base_path_list = ['path_to_tftpboot'] + alternative_static_base_path_list = ['/mnt/scripts/tftpboot/'] + ignore_warning_list = ['statement not found'] + def __init__(self, **kwargs): super(JUNOS, self).__init__(connect_port=830, **kwargs) if 'quantity_of_times_to_try_lock' in kwargs: @@ -49,13 +56,6 @@ def __init__(self, **kwargs): if 'seconds_to_wait_to_try_lock' in kwargs: self.seconds_to_wait_to_try_lock = kwargs.get('seconds_to_wait_to_try_lock') - self.configuration = None - self.quantity_of_times_to_try_lock = 3 - self.seconds_to_wait_to_try_lock = 10 - self.alternative_variable_base_path_list = ['path_to_tftpboot'] - self.alternative_static_base_path_list = ['/mnt/scripts/tftpboot/'] - self.ignore_warning_list = ['statement not found'] - def connect(self): """