diff --git a/Makefile b/Makefile index 6ae448e2e..aa3b95189 100644 --- a/Makefile +++ b/Makefile @@ -120,11 +120,16 @@ publish: clean # Containers based target rules # -start: docker-compose-sdn.yml docker-compose.yml +start_recreate: docker-compose-sdn.yml docker-compose.yml docker-compose up --force-recreate --remove-orphans -d docker-compose --file $< up --force-recreate -d +start: docker-compose-sdn.yml docker-compose.yml + docker-compose up -d + docker-compose --file $< up -d + + stop: docker-compose-sdn.yml docker-compose.yml @docker-compose down --remove-orphans @@ -172,8 +177,8 @@ endif build_img: scripts/docker/Dockerfile - docker build -t networkapi:latest --file scripts/docker/Dockerfile . - docker build -t networkapi:$(NETAPI_IMAGE_VERSION) --file scripts/docker/Dockerfile . + docker build --platform=linux/x86_64 -t networkapi:latest --file scripts/docker/Dockerfile . + docker build --platform=linux/x86_64 -t networkapi:$(NETAPI_IMAGE_VERSION) --file scripts/docker/Dockerfile . push_img: scripts/docker/push_image.sh diff --git a/README.md b/README.md index 64273687d..4b42c71aa 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,15 @@ It was not created to be and inventory database, so it does not have CMDB functi ## Documentation [Documentation](http://globonetworkapi.readthedocs.org/) +## Run API +Run `make build_img && make start` and visit `http://localhost:8000/healthcheck` if running locally. + ## Run Unit Tests -To run the unit tests just run `make build_img && make start && make test_ci` this instruction will run all the unit tests specified in the `networkapi/tests/__init__.py` file. +Run `make test_ci`. This command will run all the unit tests specified in the `networkapi/tests/__init__.py` file. + +## Create and run migrations +To create a new migration, just run into app container `cd dbmigrate; db-migrate --new=`. This step will create a enpty migration file, you must write the SQL statement. +To run a migration, just run into app container `cd dbmigrate; db-migrate`. This command will execute all migrations files not executed until now. ## How to contribute Check this out at diff --git a/dbmigrate/migrations/20240715173246_fix_fk_list_config_bgp.migration b/dbmigrate/migrations/20240715173246_fix_fk_list_config_bgp.migration new file mode 100644 index 000000000..e147642bf --- /dev/null +++ b/dbmigrate/migrations/20240715173246_fix_fk_list_config_bgp.migration @@ -0,0 +1,10 @@ +#-*- coding:utf-8 -*- +SQL_UP = u""" +ALTER TABLE equipment_list_config_bgp DROP FOREIGN KEY fk_equipment_list_config_bgp_2; +ALTER TABLE equipment_list_config_bgp ADD CONSTRAINT `fk_equipment_list_config_bgp_2` FOREIGN KEY (`id_list_config_bgp`) REFERENCES `list_config_bgp` (`id`) +""" + +SQL_DOWN = u""" +ALTER TABLE equipment_list_config_bgp DROP FOREIGN KEY fk_equipment_list_config_bgp_2; +ALTER TABLE equipment_list_config_bgp ADD CONSTRAINT `fk_equipment_list_config_bgp_2` FOREIGN KEY (`id_list_config_bgp`) REFERENCES `route_map` (`id`) +""" diff --git a/docker-compose.yml b/docker-compose.yml index c52a6d4bb..34fc80c73 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,14 +5,21 @@ services: container_name: netapi_db image: mysql:5.7 platform: linux/x86_64 + command: --default-authentication-plugin=mysql_native_password ports: - - "3306:3306" + - "33306:3306" environment: - MYSQL_ALLOW_EMPTY_PASSWORD: 1 + MYSQL_ROOT_PASSWORD: 'put_your_password_here' volumes: - netapi_db_vol:/var/lib/mysql networks: - netapi_net + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1",-p$$MYSQL_ROOT_PASSWORD ,"--silent"] + interval: 5s + timeout: 3s + retries: 2 + start_period: 15s queue: container_name: netapi_queue @@ -37,7 +44,7 @@ services: celery: container_name: netapi_celery - image: networkapi:latest + image: globocom/networkapi:latest build: context: . dockerfile: ./scripts/docker/Dockerfile @@ -55,11 +62,11 @@ services: - queue:netapi_queue - cache:netapi_cache depends_on: - - queue + - queue netapi: container_name: netapi_app - image: globocom:latest + image: globocom/networkapi:latest build: context: . dockerfile: ./scripts/docker/Dockerfile @@ -79,9 +86,8 @@ services: networks: - netapi_net depends_on: - - db - - queue - - cache + db: + condition: service_healthy links: - db:netapi_db external_links: @@ -94,4 +100,4 @@ volumes: netapi_db_vol: networks: - netapi_net: \ No newline at end of file + netapi_net: diff --git a/fast_start_test.sh b/fast_start_test.sh index e29151f8c..55a222896 100755 --- a/fast_start_test.sh +++ b/fast_start_test.sh @@ -1,5 +1,5 @@ #!/bin/sh -source venv/bin/activate +source /venv/bin/activate pip install --no-cache --upgrade pip pip install -r requirements_test.txt @@ -100,7 +100,7 @@ echo "=============== Tests for Network v6 =================" python manage.py test networkapi/api_network.tests.v3.unit.networkipv6.async.test_delete.py python manage.py test networkapi/api_network.tests.v3.unit.networkipv6.async.test_post.py python manage.py test networkapi/api_network.tests.v3.unit.networkipv6.async.test_put.py -python manage.py test networkapi/api_network.tests.v3.sanity.allocate.test_network_v4.py +python manage.py test networkapi/api_network.tests.v3.sanity.allocate.test_network_v6.py python manage.py test networkapi/api_network.tests.v3.sanity.networkipv6.sync.test_delete.py python manage.py test networkapi/api_network.tests.v3.sanity.networkipv6.sync.test_get.py python manage.py test networkapi/api_network.tests.v3.sanity.networkipv6.sync.test_post.py diff --git a/networkapi/ambiente/models.py b/networkapi/ambiente/models.py index 568a38a7a..c072dc566 100644 --- a/networkapi/ambiente/models.py +++ b/networkapi/ambiente/models.py @@ -1976,7 +1976,7 @@ def searchNextAvailableCIDR(self, subnets, network_mask=None): for idx in range(len(subnets)-1): step = int(subnets[idx+1].network_first_ip) - int(subnets[idx].network_last_ip) - 1 if step >= 2 ** (32-int(network_mask)): - subnet = NETADDR(str(NETADDR(subnets[idx].network).next().ip) + "/" + network_mask) + subnet = NETADDR(str(NETADDR(subnets[idx].network).next().ip) + "/" + str(network_mask)) if subnet.ip == subnet.network and \ not ipaddr.IPNetwork(subnet).overlaps(ipaddr.IPNetwork(subnets[idx+1].network)): return str(subnet) @@ -2000,7 +2000,7 @@ def nextAvailableCIDR(self, subnets, network, network_mask=None): elif int(network_mask) == last_subnet.prefixlen: subnet = last_subnet.next() else: - subnet = NETADDR(str(last_subnet.next().ip) + "/" + network_mask) + subnet = NETADDR(str(last_subnet.next().ip) + "/" + str(network_mask)) if not subnet.ip == subnet.network: subnet = subnet.next() diff --git a/networkapi/api_channel/facade.py b/networkapi/api_channel/facade.py index 14fbba264..c77621a92 100644 --- a/networkapi/api_channel/facade.py +++ b/networkapi/api_channel/facade.py @@ -60,6 +60,13 @@ def create(self, data): vlan_nativa = data.get('vlan') envs_vlans = data.get('envs_vlans') + ### Verify if channel number is greater than 0 and smaller or equal to 4096 + if int(nome) > 4096 or int(nome) <=1 : + log.error("Channel %s must be between 0 and 4097." % nome) + raise InterfaceError( + "Channel %s must be between 0 and 4097." % nome + ) + api_interface_facade.verificar_vlan_nativa(vlan_nativa) # Checks if Port Channel name already exists on equipment diff --git a/networkapi/api_deploy/facade.py b/networkapi/api_deploy/facade.py index 1c7c3a354..2b307e92e 100644 --- a/networkapi/api_deploy/facade.py +++ b/networkapi/api_deploy/facade.py @@ -20,7 +20,7 @@ from networkapi.api_equipment.exceptions import AllEquipmentsAreInMaintenanceException from networkapi.api_rest import exceptions as api_exceptions from networkapi.distributedlock import distributedlock -from networkapi.equipamento.models import Equipamento +from networkapi.equipamento.models import Equipamento, EquipamentoAcesso from networkapi.extra_logging import local from networkapi.extra_logging import NO_REQUEST_ID from networkapi.plugins.factory import PluginFactory @@ -65,6 +65,11 @@ def _applyconfig(equipment, filename, equipment_access=None, source_server=None, # TODO: Handle exceptions from the following methods and generate response # for the caller + # tipo_acesso = EquipamentoAcesso.search(None, + # equipment, + # ).uniqueResult() + # if tipo_acesso is None: + # return 'Equipment has no Access.' equip_plugin = PluginFactory.factory(equipment) equip_plugin.connect() diff --git a/networkapi/api_equipment/exceptions.py b/networkapi/api_equipment/exceptions.py index 47b0eb3ff..223806b60 100644 --- a/networkapi/api_equipment/exceptions.py +++ b/networkapi/api_equipment/exceptions.py @@ -7,8 +7,8 @@ class AllEquipmentsAreInMaintenanceException(APIException): status_code = status.HTTP_400_BAD_REQUEST default_detail = 'All equipments to be configured are in maintenance mode.' - def __init__(self, param=default_detail): - self.detail = param + def __init__(self, param=default_detail, equips=None): + self.detail = param if not equips else "{} Equipments: {}".format(param, equips) class EquipmentInvalidValueException(APIException): diff --git a/networkapi/api_interface/facade.py b/networkapi/api_interface/facade.py index ee149ab61..5c294dfe4 100644 --- a/networkapi/api_interface/facade.py +++ b/networkapi/api_interface/facade.py @@ -290,6 +290,9 @@ def get_interface_by_ids(interface_ids): def generate_delete_file(user, equip_id, interface_list, channel): + + log.info("generate_delete_file equip_id:%s interface_list:%s channel:%s", equip_id, interface_list, channel) + try: INTERFACE_CONFIG_TOAPPLY_REL_PATH = get_variable( 'interface_config_toapply_rel_path') @@ -297,6 +300,11 @@ def generate_delete_file(user, equip_id, interface_list, channel): 'interface_config_files_path') TEMPLATE_REMOVE_CHANNEL = get_variable('template_remove_channel') TEMPLATE_REMOVE_INTERFACE = get_variable('template_remove_interface') + + log.debug('INTERFACE_CONFIG_TOAPPLY_REL_PATH:%s', INTERFACE_CONFIG_TOAPPLY_REL_PATH) + log.debug('INTERFACE_CONFIG_FILES_PATH:%s', INTERFACE_CONFIG_FILES_PATH) + log.debug('TEMPLATE_REMOVE_CHANNEL:%s', TEMPLATE_REMOVE_CHANNEL) + log.debug('TEMPLATE_REMOVE_INTERFACE:%s', TEMPLATE_REMOVE_INTERFACE) except ObjectDoesNotExist: raise var_exceptions.VariableDoesNotExistException( 'Erro buscando a variável INTERFACE_CONFIG_TEMPLATE_PATH,' @@ -350,9 +358,12 @@ def generate_delete_file(user, equip_id, interface_list, channel): def delete_channel(user, equip_id, interface_list, channel): + log.info('delete_channel equip_id:%s interface_list:%s channel:%s', equip_id, interface_list, channel) + file_to_deploy = generate_delete_file( user, equip_id, interface_list, channel) + log.debug('file_to_deploy: %s', file_to_deploy) # TODO Deploy config file try: lockvar = LOCK_EQUIPMENT % (equip_id) @@ -510,6 +521,7 @@ def _load_template_file(equipment_id, template_type): filename_in = INTERFACE_CONFIG_TEMPLATE_PATH + \ equipment_template.roteiro.roteiro + log.debug("filename_in: %s", filename_in) # Read contents from file try: diff --git a/networkapi/api_interface/views.py b/networkapi/api_interface/views.py index 7d6d92c4b..c7b1408ad 100644 --- a/networkapi/api_interface/views.py +++ b/networkapi/api_interface/views.py @@ -41,6 +41,7 @@ from networkapi.util.decorators import prepare_search from networkapi.util.json_validate import json_validate from networkapi.util.json_validate import raise_json_validate +from networkapi.util.interface_validate import InterfaceOverrideValidator from networkapi.util.geral import create_lock from networkapi.util.geral import destroy_lock from networkapi.util.geral import render_to_json @@ -327,6 +328,10 @@ class InterfaceV3View(CustomAPIView): def get(self, request, *args, **kwargs): """URL: api/v3/interface/""" + log.info('InterfaceV3View GET') + log.info('kwargs: %s', kwargs) + log.info('search: %s', self.search) + if not kwargs.get('obj_ids'): obj_model = facade.get_interface_by_search(self.search) interfaces = obj_model['query_set'] @@ -354,6 +359,8 @@ def get(self, request, *args, **kwargs): only_main_property=only_main_property ) + log.info('get interfaces response: %s', data) + return Response(data, status=status.HTTP_200_OK) @logs_method_apiview @@ -369,8 +376,36 @@ def post(self, request, *args, **kwargs): response = list() interfaces = request.DATA + log.info('InterfaceV3View POST') + log.info('interfaces: %s', interfaces) + log.info('kwargs: %s', kwargs) json_validate(SPECS.get('interface_post')).validate(interfaces) + user_interface_str_list = [] + equipmemnt_interface_str_list = [] + for interface in interfaces.get('interfaces'): + + # User interface input, ex.: + # ex.: eth1, eth1/1, 1, Gi1/5, FF:FF:FF:FF:FF:F, int1, mgmt0, ethernet1/12, .. + interface_input = interface['interface'] + user_interface_str_list.append(interface_input) + + # Equipments from input interface + equipment_id = interface['equipment'] + obj_model = facade.get_interface_by_search( + {'extends_search': [], 'start_record': 0, 'custom_search': equipment_id, 'end_record': 1000, 'asorting_cols': ['id'], 'searchable_columns': ['equipamento__id']} + ) + equipment_list = obj_model['query_set'] + log.debug("equipment_list %s", equipment_list) + for equipment_interface in equipment_list: + equipmemnt_interface_str_list.append(equipment_interface.interface) + + # Validate interface overrinding + InterfaceOverrideValidator().check_overriding( + source_interface_str_list=user_interface_str_list, + target_interface_str_list=equipmemnt_interface_str_list + ) + for i in interfaces.get('interfaces'): try: interface = facade.create_interface(i) diff --git a/networkapi/api_neighbor/models.py b/networkapi/api_neighbor/models.py index cf85a476f..036775ca6 100644 --- a/networkapi/api_neighbor/models.py +++ b/networkapi/api_neighbor/models.py @@ -271,10 +271,12 @@ def deploy(self): list_config_bgp=entry.list_config_bgp ) if not eqpt_list_config: - EquipmentListConfig().create_v4({ - 'equipment': equipment.id, - 'list_config_bgp': entry.list_config_bgp.id - }) + self.log.debug('equipment id %s' % equipment.id) + self.log.debug('id_list_config_bgp %s' % entry.list_config_bgp.id) + # EquipmentListConfig().create_v4({ + # 'equipment': equipment.id, + # 'list_config_bgp': entry.list_config_bgp.id + # }) def undeploy(self): """Undeploy NeighborV4.""" diff --git a/networkapi/api_neighbor/v4/exceptions.py b/networkapi/api_neighbor/v4/exceptions.py index df02f2a0c..c2df486e0 100644 --- a/networkapi/api_neighbor/v4/exceptions.py +++ b/networkapi/api_neighbor/v4/exceptions.py @@ -123,27 +123,29 @@ class RemoteIpAndRemoteAsnAtDifferentEquipmentsException(APIException): status_code = status.HTTP_400_BAD_REQUEST def __init__(self, neighbor): - self.detail = u'RemoteIp id = {} and RemoteAsn id = {} belongs to ' \ - u'different Equipments'.\ - format(neighbor.remote_ip, neighbor.remote_asn) + # self.detail = u'RemoteIp id = {} and RemoteAsn id = {} belongs to ' \ + # u'different Equipments'.\ + # format(neighbor.remote_ip, neighbor.remote_asn) + self.detail = u'O ASN remoto (id: {}) não está associado a qualquer equipamento do IP (id: {}). '.\ + format(neighbor.remote_asn, neighbor.remote_ip) class LocalIpAndPeerGroupAtDifferentEnvironmentsException(APIException): status_code = status.HTTP_400_BAD_REQUEST def __init__(self, neighbor): - self.detail = u'LocalIp id = {} and PeerGroup id = {} belongs to ' \ - u'different Environments'. \ - format(neighbor.local_ip, neighbor.peer_group) + self.detail = u'Not allowed to configure BGP neighbor using this Peer Group. ' \ + u'PeerGroup id = {} is not mapped to the environment of LocalIp id = {}'.\ + format(neighbor.peer_group, neighbor.local_ip) class NeighborDuplicatedException(APIException): status_code = status.HTTP_400_BAD_REQUEST def __init__(self, neighbor): - self.detail = u'It already exists Neighbor with LocalAsn id = {}, ' \ + self.detail = u'Duplicated neighbor. A Neighbor with LocalAsn id = {}, ' \ u'LocalIp id = {}, RemoteAsn id = {} and ' \ - u'RemoteIp id = {}'.\ + u'RemoteIp id = {} already exists'.\ format(neighbor.local_asn, neighbor.local_ip, neighbor.remote_asn, neighbor.remote_ip) diff --git a/networkapi/api_rack/autoprovision.py b/networkapi/api_rack/autoprovision.py index 61648ccf9..3812706ef 100644 --- a/networkapi/api_rack/autoprovision.py +++ b/networkapi/api_rack/autoprovision.py @@ -509,8 +509,16 @@ def autoprovision_splf(rack, equips): variablestochangespine1["VLANBORDACACHOSLEAF"] = str(VLANBORDACACHOSLEAF[numero_rack][spine_num-1]) variablestochangespine1["VLANBORDACACHOSBLEAF"] = str(VLANBORDACACHOSBLEAF[numero_rack][spine_num-1]) variablestochangespine1["ASLEAF"] = str(ASLEAF[numero_rack][0]) + + ### To pop Berrini + variablestochangespine1["IPNEIGHLEAF2IPV4"] = str(IPLEAFipv4[numero_rack][spine_num]) + variablestochangespine1["IPNEIGHLEAF2IPV6"] = str(IPLEAFipv6[numero_rack][spine_num]) + + variablestochangespine1["IPNEIGHLEAFIPV4"] = str(IPLEAFipv4[numero_rack][spine_num-1]) variablestochangespine1["IPNEIGHLEAFIPV6"] = str(IPLEAFipv6[numero_rack][spine_num-1]) + + variablestochangespine1["RACK_NUM"] = str(numero_rack) if spine_num in [1, 3]: variablestochangeleaf1["SP1_HOSTNAME"] = i.get("nome") variablestochangeleaf1["INTERFACE_SP1"] = i.get("interface") diff --git a/networkapi/api_rack/facade.py b/networkapi/api_rack/facade.py index 083e22425..c3c7799cc 100644 --- a/networkapi/api_rack/facade.py +++ b/networkapi/api_rack/facade.py @@ -970,8 +970,8 @@ def allocate_env_vlan(user, rack_id): rack_env.spine_leaf_vlans_save() # leaf x leaf - rack_env.leaf_leaf_vlans_save() rack_env.leaf_leaf_envs_save() + rack_env.leaf_leaf_vlans_save() # producao/cloud rack_env.prod_environment_save() diff --git a/networkapi/api_rack/provision.py b/networkapi/api_rack/provision.py index 8dbcdf9b6..a9b671164 100644 --- a/networkapi/api_rack/provision.py +++ b/networkapi/api_rack/provision.py @@ -49,6 +49,7 @@ def replace_file(filein, fileout, dicionario): try: # Write contents to file. # Using mode 'w' truncates the file. + log.debug("Salvando arquivo: %s" % fileout) file_handle = open(fileout, 'w') file_handle.write(file_string) file_handle.close() @@ -282,28 +283,17 @@ def spine_provision(self, rack, equips): id_vlt = [envconfig.get("VLT").get("id_vlt_lf1"), envconfig.get("VLT").get("id_vlt_lf2")] priority_vlt = [envconfig.get("VLT").get("priority_vlt_lf1"), envconfig.get("VLT").get("priority_vlt_lf2")] - IPSPINEipv4[numero_rack].append(CIDRBEipv4[0][0]) - IPSPINEipv4[numero_rack].append(CIDRBEipv4[1][0]) - IPSPINEipv4[numero_rack].append(CIDRBEipv4[2][0]) - IPSPINEipv4[numero_rack].append(CIDRBEipv4[3][0]) - # - IPLEAFipv4[numero_rack].append(CIDRBEipv4[0][1]) - IPLEAFipv4[numero_rack].append(CIDRBEipv4[1][1]) - IPLEAFipv4[numero_rack].append(CIDRBEipv4[2][1]) - IPLEAFipv4[numero_rack].append(CIDRBEipv4[3][1]) + for i in range(len(CIDRBEipv4)): + IPSPINEipv4[numero_rack].append(CIDRBEipv4[i][0]) + IPLEAFipv4[numero_rack].append(CIDRBEipv4[i][1]) + + IPSPINEipv6[numero_rack].append(CIDRBEipv6[i][0]) + IPLEAFipv6[numero_rack].append(CIDRBEipv6[i][1]) + # IPSIBGPipv4[numero_rack].append(IBGPToRLxLipv4[0]) IPSIBGPipv4[numero_rack].append(IBGPToRLxLipv4[1]) # - IPSPINEipv6[numero_rack].append(CIDRBEipv6[0][0]) - IPSPINEipv6[numero_rack].append(CIDRBEipv6[1][0]) - IPSPINEipv6[numero_rack].append(CIDRBEipv6[2][0]) - IPSPINEipv6[numero_rack].append(CIDRBEipv6[3][0]) - # - IPLEAFipv6[numero_rack].append(CIDRBEipv6[0][1]) - IPLEAFipv6[numero_rack].append(CIDRBEipv6[1][1]) - IPLEAFipv6[numero_rack].append(CIDRBEipv6[2][1]) - IPLEAFipv6[numero_rack].append(CIDRBEipv6[3][1]) # IPSIBGPipv6[numero_rack].append(IBGPToRLxLipv6[0]) IPSIBGPipv6[numero_rack].append(IBGPToRLxLipv6[1]) @@ -322,6 +312,10 @@ def spine_provision(self, rack, equips): log.debug("as") log.debug(BASE_AS_LFS) log.debug(numero_rack) + log.debug(numero_rack) + log.debug("zip equips_sorted") + log.debug(zip(equips_sorted[:2], [0, 2], [0, 1])) + ASLEAF[numero_rack].append(BASE_AS_LFS + numero_rack) for equip, spn, j in zip(equips_sorted[:2], [0, 2], [0, 1]): @@ -342,6 +336,19 @@ def spine_provision(self, rack, equips): variablestochangeleaf1["VLANBORDACACHOSLEAFSP2"] = str(vlanBOCA[spn + 1]) variablestochangeleaf1["VLANBORDACACHOSBLEAFSP1"] = str(vlanBOCAB[spn]) variablestochangeleaf1["VLANBORDACACHOSBLEAFSP2"] = str(vlanBOCAB[spn + 1]) + + ### To pop Berrini + variablestochangeleaf1["VLANBERBELEAFSP1"] = str(vlanBE[int(equip.get("nome")[-1]) - 1]) + variablestochangeleaf1["VLANBERBELEAFSP2"] = str(vlanBE[int(equip.get("nome")[-1]) + 1]) + variablestochangeleaf1["VLANBERFELEAFSP1"] = str(vlanFE[int(equip.get("nome")[-1]) - 1]) + variablestochangeleaf1["VLANBERFELEAFSP2"] = str(vlanFE[int(equip.get("nome")[-1]) + 1]) + variablestochangeleaf1["VLANBERBORDALEAFSP1"] = str(vlanBO[int(equip.get("nome")[-1]) - 1]) + variablestochangeleaf1["VLANBERBORDALEAFSP2"] = str(vlanBO[int(equip.get("nome")[-1]) + 1]) + variablestochangeleaf1["VLANBERBORDACACHOSLEAFSP1"] = str(vlanBOCA[int(equip.get("nome")[-1]) - 1]) + variablestochangeleaf1["VLANBERBORDACACHOSLEAFSP2"] = str(vlanBOCA[int(equip.get("nome")[-1]) + 1]) + variablestochangeleaf1["VLANBERBORDACACHOSBLEAFSP1"] = str(vlanBOCAB[int(equip.get("nome")[-1]) - 1]) + variablestochangeleaf1["VLANBERBORDACACHOSBLEAFSP2"] = str(vlanBOCAB[int(equip.get("nome")[-1]) + 1]) + log.debug("2") variablestochangeleaf1["ASLEAF"] = str(ASLEAF[numero_rack][0]) @@ -397,12 +404,16 @@ def spine_provision(self, rack, equips): variablestochangeleaf1["OWN_IP_MGMT"] = equip.get("ip_mngt") variablestochangeleaf1["LF_HOSTNAME"] = equip.get("nome") log.debug("5") + lf1_if_counter = 1 + lf2_if_counter = 1 for i in equip.get("interfaces"): if i.get("nome")[:3] == self.leaf_prefix: variablestochangeleaf1["LFNEIGH_HOSTNAME"] = i.get("nome") variablestochangeleaf1["LFNEIGH_IP_MGMT"] = i.get("ip_mngt") elif i.get("nome")[:3] == self.spine_prefix: spine_num = int(i.get("nome")[-1]) + log.debug(spn) + log.debug(spine_num) variablestochangespine1["ASSPINE"] = str(BASE_AS_SPN + spine_num - 1) variablestochangespine1["INTERFACE"] = i.get("interface") variablestochangespine1["LEAFNAME"] = equip.get("nome") @@ -416,6 +427,91 @@ def spine_provision(self, rack, equips): variablestochangespine1["VLANBORDACACHOSLEAF"] = str(vlanBOCA[spine_num - 1]) variablestochangespine1["VLANBORDACACHOSB"] = str(vlanBOCAB[spine_num - 1]) variablestochangespine1["ASLEAF"] = str(ASLEAF[numero_rack][0]) + + ### TO BERRINI + ## To pop Berrini we use new variables, this make the new cross topology and old topology run to. + + log.debug(spine_num) + if spine_num == 1: + variablestochangespine1["VLANBORDA2LEAF"] = str(vlanBO[spine_num]) + variablestochangespine1["VLANBORDA2CACHOSLEAF"] = str(vlanBOCA[spine_num]) + variablestochangespine1["VLANBORDA2CACHOSB"] = str(vlanBOCAB[spine_num]) + variablestochangespine1["VLANFE2LEAF"] = str(vlanFE[spine_num]) + variablestochangespine1["VLANBE2LEAF"] = str(vlanBE[spine_num]) + variablestochangespine1["VLANBE1LEAF"] = str(vlanBE[spine_num - 1]) + variablestochangespine1["VLANFE1LEAF"] = str(vlanFE[spine_num - 1]) + variablestochangespine1["VLANBORDA1LEAF"] = str(vlanBO[spine_num - 1]) + variablestochangespine1["VLANBORDA1CACHOSLEAF"] = str(vlanBOCA[spine_num - 1]) + variablestochangespine1["VLANBORDA1CACHOSB"] = str(vlanBOCAB[spine_num - 1]) + + counter = 0 + for ipv6 in IPSPINEipv6[numero_rack]: + if counter + 1 < len(IPSPINEipv6[numero_rack]): + variablestochangespine1["IPSPINE{}IPV6".format(counter+1)] = str(IPSPINEipv6[numero_rack][counter - 1]) + variablestochangespine1["IPNEIGHLEAF{}IPV6".format(counter+1)] = str(IPLEAFipv6[numero_rack][counter -1]) + + counter += 1 + + counter = 0 + for ipv4 in IPSPINEipv4[numero_rack]: + if counter + 1 < len(IPSPINEipv4[numero_rack]): + variablestochangespine1["IPSPINE{}IPV4".format(counter+1)] = str(IPSPINEipv4[numero_rack][counter - 1]) + variablestochangespine1["IPNEIGHLEAF{}IPV4".format(counter+1)] = str(IPLEAFipv4[numero_rack][counter -1]) + + counter += 1 + + + elif spine_num == 2: + variablestochangespine1["VLANBORDA2LEAF"] = str(vlanBO[spine_num]) + variablestochangespine1["VLANBORDA2CACHOSLEAF"] = str(vlanBOCA[spine_num]) + variablestochangespine1["VLANBORDA2CACHOSB"] = str(vlanBOCAB[spine_num]) + variablestochangespine1["VLANFE2LEAF"] = str(vlanFE[spine_num]) + variablestochangespine1["VLANBE2LEAF"] = str(vlanBE[spine_num]) + variablestochangespine1["VLANBE1LEAF"] = str(vlanBE[spine_num + 1]) + variablestochangespine1["VLANFE1LEAF"] = str(vlanFE[spine_num + 1]) + variablestochangespine1["VLANBORDA1LEAF"] = str(vlanBO[spine_num + 1]) + variablestochangespine1["VLANBORDA1CACHOSLEAF"] = str(vlanBOCA[spine_num + 1]) + variablestochangespine1["VLANBORDA1CACHOSB"] = str(vlanBOCAB[spine_num + 1]) + + counter = 0 + for ipv6 in IPSPINEipv6[numero_rack]: + if spine_num + counter < len(IPLEAFipv6[numero_rack]): + variablestochangespine1["IPSPINE{}IPV6".format(counter + 1)] = str(IPSPINEipv6[numero_rack][counter + 1]) + variablestochangespine1["IPNEIGHLEAF{}IPV6".format(counter + 1)] = str(IPLEAFipv6[numero_rack][counter + 1]) + counter += 1 + + counter = 0 + for ipv4 in IPSPINEipv4[numero_rack]: + if spine_num + counter < len(IPLEAFipv4[numero_rack]): + variablestochangespine1["IPSPINE{}IPV4".format(counter + 1)] = str(IPSPINEipv4[numero_rack][counter + 1]) + variablestochangespine1["IPNEIGHLEAF{}IPV4".format(counter + 1)] = str(IPLEAFipv4[numero_rack][counter + 1]) + + + counter += 1 + interface1_counter = 1 + interface2_counter = 1 + + for lf in equips_sorted[:2]: + for iface in lf.get("interfaces"): + iface_name = iface.get("nome") + if iface_name[:3] == self.spine_prefix and int(iface_name[-1]) == 2: + variablestochangespine1["SINGLE2{}INT".format(interface2_counter)] = iface.get("interface") + variablestochangespine1["INT_LF_2{}UPLINK".format(interface2_counter)] = iface.get("eq_interface") + variablestochangespine1["DESCRIPTION2CONNECT"] = equips_sorted[0].get("nome") + variablestochangespine1["DESCRIPTION1CONNECT"] = equips_sorted[1].get("nome") + variablestochangespine1["NUM_CHANNEL"] = variablestochangespine1["SINGLE21INT"].split('/')[-1].split(':')[0] + + interface2_counter += 1 + + elif iface_name[:3] == self.spine_prefix and int(iface_name[-1]) == 1: + variablestochangespine1["SINGLE1{}INT".format(interface1_counter)] = iface.get("interface") + variablestochangespine1["INT_LF_1{}UPLINK".format(interface1_counter)] = iface.get("eq_interface") + variablestochangespine1["NUM_CHANNEL"] = variablestochangespine1["SINGLE11INT"].split('/')[-1].split(':')[0] + variablestochangespine1["DESCRIPTION1CONNECT"] = equips_sorted[0].get("nome") + variablestochangespine1["DESCRIPTION2CONNECT"] = equips_sorted[1].get("nome") + interface1_counter += 1 + #### END to Berrini Block ##### + variablestochangespine1["IPNEIGHLEAFIPV4"] = str(IPLEAFipv4[numero_rack][spine_num - 1]) variablestochangespine1["IPNEIGHLEAFIPV6"] = str(IPLEAFipv6[numero_rack][spine_num - 1]) @@ -423,6 +519,7 @@ def spine_provision(self, rack, equips): variablestochangeleaf1["SP1_HOSTNAME"] = i.get("nome") variablestochangeleaf1["INTERFACE_SP1"] = i.get("interface") variablestochangeleaf1["ASSPINE1"] = str(BASE_AS_SPN + spine_num - 1) + else: variablestochangeleaf1["SP2_HOSTNAME"] = i.get("nome") variablestochangeleaf1["INTERFACE_SP2"] = i.get("interface") @@ -430,6 +527,46 @@ def spine_provision(self, rack, equips): fileinspine1 = path_to_guide + i.get("roteiro") fileoutspine1 = path_to_add_config + i.get("nome") + "-ADD-" + rack.nome + ".cfg" + + ### ANOTHER BERRINI BLOCK + ## Here we invert the variables of description, due unique template to diferent spines + ## without this invertion, the description run well on spine 2 but on spine 1 they are inverted + if int(i.get("nome")[-1]) == 1: + spn_int_desc1 = variablestochangespine1["DESCRIPTION2CONNECT"] + spn_int_desc2 = variablestochangespine1["DESCRIPTION1CONNECT"] + variablestochangespine1["DESCRIPTION1CONNECT"] = spn_int_desc2 + variablestochangespine1["DESCRIPTION2CONNECT"] = spn_int_desc1 + + if int(i.get("nome")[-1]) == 2: + spn_int_desc1 = variablestochangespine1["DESCRIPTION2CONNECT"] + spn_int_desc2 = variablestochangespine1["DESCRIPTION1CONNECT"] + variablestochangespine1["DESCRIPTION2CONNECT"] = spn_int_desc2 + variablestochangespine1["DESCRIPTION1CONNECT"] = spn_int_desc1 + + ipv4spn1 = variablestochangespine1["IPSPINE1IPV4"] + ipv4spn2 = variablestochangespine1["IPSPINE2IPV4"] + variablestochangespine1["IPSPINE1IPV4"] = ipv4spn2 + variablestochangespine1["IPSPINE2IPV4"] = ipv4spn1 + + ipv6spn1 = variablestochangespine1["IPSPINE1IPV6"] + ipv6spn2 = variablestochangespine1["IPSPINE2IPV6"] + variablestochangespine1["IPSPINE1IPV6"] = ipv6spn2 + variablestochangespine1["IPSPINE2IPV6"] = ipv6spn1 + + ipv4_nei_lf1 = variablestochangespine1["IPNEIGHLEAF1IPV4"] + ipv4_nei_lf2 = variablestochangespine1["IPNEIGHLEAF2IPV4"] + variablestochangespine1["IPNEIGHLEAF1IPV4"] = ipv4_nei_lf2 + variablestochangespine1["IPNEIGHLEAF2IPV4"] = ipv4_nei_lf1 + + ipv6_nei_lf1 = variablestochangespine1["IPNEIGHLEAF1IPV6"] + ipv6_nei_lf2 = variablestochangespine1["IPNEIGHLEAF2IPV6"] + variablestochangespine1["IPNEIGHLEAF1IPV6"] = ipv6_nei_lf2 + variablestochangespine1["IPNEIGHLEAF2IPV6"] = ipv6_nei_lf1 + + + + #### END BLOCK + self.replace_file(fileinspine1, fileoutspine1, variablestochangespine1) variablestochangespine1 = dict() @@ -437,6 +574,101 @@ def spine_provision(self, rack, equips): variablestochangeleaf1["HOSTNAME_OOB"] = i.get("nome") variablestochangeleaf1["INTERFACE_OOB"] = i.get("interface") + interface_name = i.get("nome") + if interface_name[:3] == self.spine_prefix and int(interface_name[-1]) == 1: + variablestochangeleaf1["INTERFACE{}_SP1".format(lf1_if_counter)] = i.get("interface") + lf1_if_counter +=1 + + elif interface_name[:3] == self.spine_prefix and int(interface_name[-1]) == 2: + variablestochangeleaf1["INTERFACE{}_SP2".format(lf2_if_counter)] = i.get("interface") + lf2_if_counter +=1 + + #### ANOTHER BERRINI BLOCK + ## Here we invert the variables of description, due unique template to diferent spines + ## without this invertion, the description run well on spine 2 but on spine 1 they are inverted + variablestochangeleaf1['DFS_SOURCE_IP'] = '10.126.24.12' + variablestochangeleaf1['DFS_PEER_IP'] = '10.126.24.13' + variablestochangeleaf1["INT_GERENCIA_OOB"] = "47" + log.debug(variablestochangeleaf1) + if int(equip.get('nome')[-1]) == 2: + ### Here we must have to invert the variables to leaf2, due the new topology + # sp1_hostname = variablestochangeleaf1["SP1_HOSTNAME"] + sp2_hostname = variablestochangeleaf1["SP2_HOSTNAME"] + variablestochangeleaf1["SP1_HOSTNAME"] = sp2_hostname + # variablestochangeleaf1["SP2_HOSTNAME"] = sp1_hostname + + # if1_sp1 = variablestochangeleaf1['INTERFACE1_SP1'] + # if2_sp1 = variablestochangeleaf1['INTERFACE2_SP1'] + # if3_sp1 = variablestochangeleaf1['INTERFACE3_SP1'] + # if4_sp1 = variablestochangeleaf1['INTERFACE4_SP1'] + + if1_sp2 = variablestochangeleaf1['INTERFACE1_SP2'] + # if2_sp2 = variablestochangeleaf1['INTERFACE2_SP2'] + # if3_sp2 = variablestochangeleaf1['INTERFACE3_SP2'] + # if4_sp2 = variablestochangeleaf1['INTERFACE4_SP2'] + + variablestochangeleaf1['INTERFACE1_SP1'] = if1_sp2 + # variablestochangeleaf1['INTERFACE2_SP1'] = if2_sp2 + # variablestochangeleaf1['INTERFACE3_SP1'] = if3_sp2 + # variablestochangeleaf1['INTERFACE4_SP1'] = if4_sp2 + + # variablestochangeleaf1['INTERFACE1_SP2'] = if1_sp1 + # variablestochangeleaf1['INTERFACE2_SP2'] = if2_sp1 + # variablestochangeleaf1['INTERFACE3_SP2'] = if3_sp1 + # variablestochangeleaf1['INTERFACE4_SP2'] = if4_sp1 + + vlan_bo_leaf_sp1 = variablestochangeleaf1["VLANBORDALEAFSP1"] + vlan_bo_leaf_sp2 = variablestochangeleaf1["VLANBORDALEAFSP2"] + + variablestochangeleaf1["VLANBORDALEAFSP1"] = vlan_bo_leaf_sp2 + variablestochangeleaf1["INT_GERENCIA_OOB"] = "48" + # variablestochangeleaf1["VLANBORDALEAFSP1"] = str(vlanBO[spn]) + # variablestochangeleaf1["VLANBORDALEAFSP2"] = str(vlanBO[spn + 1]) + + + vlan_ber_be1= variablestochangeleaf1["VLANBERBELEAFSP1"] + vlan_ber_be2= variablestochangeleaf1["VLANBERBELEAFSP2"] + vlan_ber_fe1= variablestochangeleaf1["VLANBERFELEAFSP1"] + vlan_ber_fe2= variablestochangeleaf1["VLANBERFELEAFSP2"] + vlan_ber_bo1= variablestochangeleaf1["VLANBERBORDALEAFSP1"] + vlan_ber_bo2= variablestochangeleaf1["VLANBERBORDALEAFSP2"] + vlan_ber_bc1= variablestochangeleaf1["VLANBERBORDACACHOSLEAFSP1"] + vlan_ber_bc2= variablestochangeleaf1["VLANBERBORDACACHOSLEAFSP2"] + vlan_ber_bcb1= variablestochangeleaf1["VLANBERBORDACACHOSBLEAFSP1"] + vlan_ber_bcb2= variablestochangeleaf1["VLANBERBORDACACHOSBLEAFSP2"] + variablestochangeleaf1["VLANBERBELEAFSP1"] = vlan_ber_be2 + variablestochangeleaf1["VLANBERBELEAFSP2"] = vlan_ber_be1 + variablestochangeleaf1["VLANBERFELEAFSP1"] = vlan_ber_fe2 + variablestochangeleaf1["VLANBERFELEAFSP2"] = vlan_ber_fe1 + variablestochangeleaf1["VLANBERBORDALEAFSP1"] = vlan_ber_bo2 + variablestochangeleaf1["VLANBERBORDALEAFSP2"] = vlan_ber_bo1 + variablestochangeleaf1["VLANBERBORDACACHOSLEAFSP1"] = vlan_ber_bc2 + variablestochangeleaf1["VLANBERBORDACACHOSLEAFSP2"] = vlan_ber_bc1 + variablestochangeleaf1["VLANBERBORDACACHOSBLEAFSP1"] = vlan_ber_bcb2 + variablestochangeleaf1["VLANBERBORDACACHOSBLEAFSP2"] = vlan_ber_bcb1 + + variablestochangeleaf1['DFS_SOURCE_IP'] = '10.126.24.13' + variablestochangeleaf1['DFS_PEER_IP'] = '10.126.24.12' + # as_spine1 = variablestochangeleaf1["ASSPINE1"] + as_spine2 = variablestochangeleaf1["ASSPINE2"] + variablestochangeleaf1["ASSPINE1"] = as_spine2 + # variablestochangeleaf1["ASSPINE2"] = as_spine1 + + # variablestochangeleaf1['LFPO1'] = variablestochangeleaf1['INTERFACE1_SP1'].split('/')[-1].split(':')[0] + # variablestochangeleaf1['LFPO2'] = variablestochangeleaf1['INTERFACE1_SP2'].split('/')[-1].split(':')[0] + variablestochangeleaf1["BASE_NETWORK_HOST_FE_IPV4"] = variablestochangeleaf1["NET_HOST_FE_IPV4"].split("/")[0] + + variablestochangeleaf1["BASE_NETWORK_HOST_BE_IPV4"] = variablestochangeleaf1["NET_HOST_BE_IPV4"].split("/")[0] + + variablestochangeleaf1["BASE_NETWORK_HOST_FE_IPV6"] = variablestochangeleaf1["NET_HOST_FE_IPV6"].split("/")[0] + variablestochangeleaf1["BASE_NETWORK_HOST_BE_IPV6"] = variablestochangeleaf1["NET_HOST_BE_IPV6"].split("/")[0] + variablestochangeleaf1['BASE_MASK_HOST_BE_IPV6'] = variablestochangeleaf1["NET_HOST_BE_IPV6"].split("/")[1] + + variablestochangeleaf1['BASE_NETWORK_HOST_BO_DSR_IPV6'] = variablestochangeleaf1['NET_HOST_BO_DSR_IPV6'].split('/')[0] + variablestochangeleaf1['BASE_MASK_HOST_BO_DSR_IPV6'] = variablestochangeleaf1['NET_HOST_BO_DSR_IPV6'].split('/')[1] + + ### End Berrini Block + variablestochangeleaf1["ID_VLT"] = str(id_vlt[j]) variablestochangeleaf1["PRIORITY_VLT"] = str(priority_vlt[j]) @@ -516,6 +748,7 @@ def oob_provision(self, equips): variablestochangeoob["OWN_IP_MGMT"] = oob.get("ip_mngt") variablestochangeoob["HOSTNAME_OOB"] = oob.get("nome") variablestochangeoob["HOSTNAME_RACK"] = self.rack.nome + log.debug(equips_sorted) fileinoob = path_to_guide + oob.get("roteiro") fileoutoob = path_to_config + oob.get("nome") + ".cfg" @@ -524,6 +757,18 @@ def oob_provision(self, equips): nome = equip.get("nome") log.debug(str(nome)) roteiro = equip.get("roteiro") + + ### To Pop Berrini + base_leaf_hostname = "LF-" + variablestochangeoob['HOSTNAME_OOB'].split('-')[1] + "-R"+ str(self.rack.numero) +"-" + variablestochangeoob['LEAF1_HOSTNAME'] = base_leaf_hostname + "1" + variablestochangeoob['LEAF2_HOSTNAME'] = base_leaf_hostname + "2" + + variablestochangecore1["CHANNEL_NUMBER"] = str(self.rack.numero) + variablestochangecore2["CHANNEL_NUMBER"] = str(self.rack.numero) + variablestochangecore1["RACK_NUMBER"] = str(self.rack.numero) + variablestochangecore2["RACK_NUMBER"] = str(self.rack.numero) + ### End Block + if nome[:3] == self.leaf_prefix: if nome[-1] == "1": variablestochangeoob["HOSTNAME_LF1"] = nome diff --git a/networkapi/api_rack/rackenvironments.py b/networkapi/api_rack/rackenvironments.py index a3a932e50..598352ed6 100644 --- a/networkapi/api_rack/rackenvironments.py +++ b/networkapi/api_rack/rackenvironments.py @@ -56,12 +56,14 @@ def spines_environment_save(self): environment_spn_lf_list = list() spines = int(self.rack.dcroom.spines) fabric = self.rack.dcroom.name + rackname = self.rack.nome + grupoL3name = fabric+"_"+rackname try: - id_grupo_l3 = models_env.GrupoL3().get_by_name(fabric).id + id_grupo_l3 = models_env.GrupoL3().get_by_name(grupoL3name).id except: grupo_l3_dict = models_env.GrupoL3() - grupo_l3_dict.nome = fabric + grupo_l3_dict.nome = grupoL3name grupo_l3_dict.save() id_grupo_l3 = grupo_l3_dict.id pass @@ -89,11 +91,15 @@ def spines_environment_save(self): id_amb_log = amb_log_dict.id pass config = list() + subnet_index = (spn*int(self.rack.dcroom.racks))+int(self.rack.numero) for sub in config_subnet: config_spn = { - 'subnet': str(sub.get("cidr")[spn]), + 'subnet': str(sub.get("cidr")[subnet_index]), + 'network': str(sub.get("cidr")[subnet_index]), 'new_prefix': str(31) if str(sub.get("type"))[-1] is "4" else str(127), + 'subnet_mask': str(31) if str(sub.get("type"))[-1] is "4" else str(127), 'type': str(sub.get("type")), + 'ip_version': str(sub.get("type")), 'network_type': sub.get("network_type") } config.append(config_spn) @@ -105,16 +111,17 @@ def spines_environment_save(self): 'ipv4_template': env.ipv4_template, 'ipv6_template': env.ipv6_template, 'link': env.link, - 'min_num_vlan_2': env.min_num_vlan_2, - 'max_num_vlan_2': env.max_num_vlan_2, - 'min_num_vlan_1': env.min_num_vlan_1, - 'max_num_vlan_1': env.max_num_vlan_1, + 'min_num_vlan_2': env.min_num_vlan_1+subnet_index, + 'max_num_vlan_2': env.min_num_vlan_1+subnet_index, + 'min_num_vlan_1': env.min_num_vlan_1+subnet_index, + 'max_num_vlan_1': env.min_num_vlan_1+subnet_index, 'vrf': env.vrf, 'father_environment': env.id, 'default_vrf': env.default_vrf.id, 'configs': config, 'fabric_id': self.rack.dcroom.id } + environment = facade_env.create_environment(obj) return environment_spn_lf_list @@ -123,9 +130,12 @@ def spines_environment_read(self): def spine_leaf_vlans_save(self): log.debug("_create_spnlfvlans") + fabric = self.rack.dcroom.name + rackname = self.rack.nome + grupoL3name = fabric+"_"+rackname spn_lf_envs = models_env.Ambiente.objects.filter(dcroom=int(self.rack.dcroom.id), - grupo_l3__nome=str(self.rack.dcroom.name), + grupo_l3__nome=str(grupoL3name), ambiente_logico__nome__in=["SPINE01LEAF", "SPINE02LEAF", "SPINE03LEAF", @@ -178,7 +188,7 @@ def leaf_leaf_vlans_save(self): log.debug("_create_lflf_vlans") env_lf = models_env.Ambiente.objects.filter(dcroom=int(self.rack.dcroom.id), - grupo_l3__nome=str(self.rack.dcroom.name), + grupo_l3__nome=str(self.rack.nome), ambiente_logico__nome="LEAF-LEAF") log.debug("Leaf-leaf environments: " + str(env_lf)) @@ -478,13 +488,29 @@ def manage_vlan_save(self): def rack_environment_remove(self): log.info("_remove_envs") - envs = models_env.Ambiente.objects.filter(dcroom=int(self.rack.dcroom.id), + envs_racks = models_env.Ambiente.objects.filter(dcroom=int(self.rack.dcroom.id), grupo_l3__nome=str(self.rack.nome)) - for env in envs: + log.debug("PROD RACK environments to be removed: %s. Total: %s" % (str(envs_racks), len(envs_racks))) + + for env in envs_racks: + env.delete_v3() + + log.debug("PROD RACK environments removed.") + + envs_spines = models_env.Ambiente.objects.filter(dcroom=int(rack.dcroom.id), + grupo_l3__nome=str(rack.dcroom.name), + ambiente_logico__nome__in=["SPINE01LEAF", + "SPINE02LEAF", + "SPINE03LEAF", + "SPINE04LEAF"]) + + log.debug("PROD SPINELEAF environments to be removed: %s. Total: %s" % (str(envs_spines), len(envs_spines))) + + for env in envs_spines: env.delete_v3() - log.debug("PROD environments: %s. Total: %s" % (str(envs), len(envs))) + log.debug("PROD SPINELEAF environments removed.") def rack_vlans_remove(self): log.info("remove_vlans") diff --git a/networkapi/api_vip_request/facade/v3.py b/networkapi/api_vip_request/facade/v3.py index 7d2780e03..5af635b9e 100644 --- a/networkapi/api_vip_request/facade/v3.py +++ b/networkapi/api_vip_request/facade/v3.py @@ -156,6 +156,8 @@ def get_vip_request_by_search(search=dict()): def prepare_apply(load_balance, vip, created=True, user=None): + log.debug("Preparing to aplly. load_balance:%s vip:%s, created:%s", load_balance, vip, created) + vip_request = copy.deepcopy(vip) id_vip = str(vip_request.get('id')) @@ -617,6 +619,9 @@ def patch_real_vip_request(vip_requests, user): @commit_on_success def delete_real_vip_request(vip_requests, user, cleanup='0'): + + log.debug("Deleting real VIP request. vip_requests:%s cleanup:%s", vip_requests, cleanup) + load_balance = dict() cleanup = True if cleanup == '1' else False @@ -656,6 +661,8 @@ def delete_real_vip_request(vip_requests, user, cleanup='0'): def _validate_vip_to_apply(vip_request, update=False, user=None): + log.debug("Validating VIP to aplly. vip_request:%s update:%s", vip_request, update) + vip = get_vip_request_by_id(vip_request.get('id')) # validate vip with same ipv4 ou ipv6 @@ -676,11 +683,12 @@ def _validate_vip_to_apply(vip_request, update=False, user=None): raise exceptions.VipRequestAlreadyCreated(vip.id) equips = facade_eqpt.get_eqpt_by_envvip(vip_request['environmentvip']) + log.debug("equips:%s", equips) conf = EnvironmentVip.objects.get(id=vip_request['environmentvip']).conf - if facade_eqpt.all_equipments_are_in_maintenance(equips): - raise exceptions_eqpt.AllEquipmentsAreInMaintenanceException() + if equips and facade_eqpt.all_equipments_are_in_maintenance(equips): + raise exceptions_eqpt.AllEquipmentsAreInMaintenanceException(equips=equips) if user: if not facade_eqpt.all_equipments_can_update_config(equips, user): diff --git a/networkapi/api_vip_request/views/v3.py b/networkapi/api_vip_request/views/v3.py index bb54b1148..3536eb2fd 100644 --- a/networkapi/api_vip_request/views/v3.py +++ b/networkapi/api_vip_request/views/v3.py @@ -74,6 +74,8 @@ def delete(self, request, *args, **kwargs): """ + log.debug("Undeploying vip request. kwargs: %s", kwargs) + vip_request_ids = kwargs['obj_ids'].split(';') cleanup = request.GET.get('cleanup', '0') diff --git a/networkapi/infrastructure/xml_utils.py b/networkapi/infrastructure/xml_utils.py index 356ab7db3..2c92110cd 100644 --- a/networkapi/infrastructure/xml_utils.py +++ b/networkapi/infrastructure/xml_utils.py @@ -55,7 +55,8 @@ def _add_text_node(value, node, doc): if not isinstance(value, StringTypes): text = '%s' % unicode(value) else: - text = r'%s' % value.replace('%', '%%') + # text = r'%s' % value.replace('%', '%%') + text = value try: textNode = doc.createTextNode(text) diff --git a/networkapi/interface/resource/InterfaceChannelResource.py b/networkapi/interface/resource/InterfaceChannelResource.py index c6498a7d1..b2eff32ca 100644 --- a/networkapi/interface/resource/InterfaceChannelResource.py +++ b/networkapi/interface/resource/InterfaceChannelResource.py @@ -277,11 +277,15 @@ def handle_get(self, request, user, *args, **kwargs): return self.response_error(1) def handle_delete(self, request, user, *args, **kwargs): - """Trata uma requisição DELETE para excluir um port channel - URL: /channel/delete// + """ + Delete port channel from interface id (table 'interfaces'), ex.: + DELETE /channel/delete/73886/ + + Not tested: + DELETE /channel/delete// """ try: - self.log.info('Delete Channel') + self.log.debug('Delete Channel request:%s', request) # User permission if not has_perm(user, AdminPermission.EQUIPMENT_MANAGEMENT, AdminPermission.WRITE_OPERATION): @@ -290,30 +294,39 @@ def handle_delete(self, request, user, *args, **kwargs): raise UserNotAuthorizedError(None) interface_id = kwargs.get('channel_name') + self.log.info('interface_id: %s', interface_id) interface = Interface.get_by_pk(int(interface_id)) equip_list = [] + # Getting port channel try: interface.channel.id channel = interface.channel except: channel = interface.ligacao_front.channel pass + self.log.debug('channel id:%s name:%s', channel.id, channel) + + # Getting all interfaces used by port channel try: interfaces = Interface.objects.all().filter(channel__id=channel.id) except: return self.response(dumps_networkapi({})) + self.log.debug('interfaces: %s', interfaces) + # Getting all equipment from interfaces (switches, ex.: LF-CM-AH18-1 and LF-CM-AH18-2) for i in interfaces: equip_list.append(i.equipamento.id) equip_list = set(equip_list) equip_dict = dict() + self.log.debug('equip_list: %s', equip_list) for e in equip_list: equip_dict[str(e)] = interfaces.filter(equipamento__id=e) tipo = TipoInterface() tipo = tipo.get_by_name('access') + # For each equipment (leaf), remove port channel for e in equip_dict: for i in equip_dict.get(e): try: diff --git a/networkapi/middlewares.py b/networkapi/middlewares.py index 4f5f1aef9..3339a4fea 100644 --- a/networkapi/middlewares.py +++ b/networkapi/middlewares.py @@ -65,12 +65,18 @@ def process_request(self, request): AuditRequest.new_request(request.get_full_path(), request.user, ip, identity, context) else: - user_auth_tuple = BasicAuthentication().authenticate(request) + try: + user_auth_tuple = BasicAuthentication().authenticate(request) + except Exception as ex: + user_auth_tuple = None if user_auth_tuple is not None: user, token = user_auth_tuple else: # keeps compatibility with old authentication method - user = RestResource.authenticate_user(request) + try: + user = RestResource.authenticate_user(request) + except Exception as ex: + user = None if user is not None: ip = self._get_ip(request) diff --git a/networkapi/plugins/Dell/FTOS/BGP/Cli.py b/networkapi/plugins/Dell/FTOS/BGP/Cli.py index 811f2aba2..1028b77ac 100644 --- a/networkapi/plugins/Dell/FTOS/BGP/Cli.py +++ b/networkapi/plugins/Dell/FTOS/BGP/Cli.py @@ -76,8 +76,8 @@ def _deploy_pre_req(self, neighbor): for rm_entry in rms: list_config_bgp = rm_entry.list_config_bgp - if not list_config_bgp.equipments.filter(id=self.equipment.id): - self.deploy_list_config_bgp(list_config_bgp) + # if not list_config_bgp.equipments.filter(id=self.equipment.id): + # self.deploy_list_config_bgp(list_config_bgp) if not route_map_in.equipments.filter(id=self.equipment.id): self.deploy_route_map(neighbor.peer_group.route_map_in) diff --git a/networkapi/plugins/Juniper/JUNOS/BGP/Cli.py b/networkapi/plugins/Juniper/JUNOS/BGP/Cli.py index b99ca5172..7a35e7d4b 100644 --- a/networkapi/plugins/Juniper/JUNOS/BGP/Cli.py +++ b/networkapi/plugins/Juniper/JUNOS/BGP/Cli.py @@ -65,14 +65,14 @@ def _deploy_pre_req(self, neighbor): route_map_out.route_map_entries for rm_entry in rms: list_config_bgp = rm_entry.list_config_bgp - if not list_config_bgp.equipments.filter(id=self.equipment.id): - self.deploy_list_config_bgp(list_config_bgp) + # if not list_config_bgp.equipments.filter(id=self.equipment.id): + # self.deploy_list_config_bgp(list_config_bgp) - if not route_map_in.equipments.filter(id=self.equipment.id): - self.deploy_route_map(neighbor.peer_group.route_map_in) + # if not route_map_in.equipments.filter(id=self.equipment.id): + # self.deploy_route_map(neighbor.peer_group.route_map_in) - if not route_map_out.equipments.filter(id=self.equipment.id): - self.deploy_route_map(neighbor.peer_group.route_map_out) + # if not route_map_out.equipments.filter(id=self.equipment.id): + # self.deploy_route_map(neighbor.peer_group.route_map_out) def _undeploy_pre_req(self, neighbor, ip_version): log.info("_undeploy_pre_req") diff --git a/networkapi/plugins/Netconf/BGP/Cli.py b/networkapi/plugins/Netconf/BGP/Cli.py new file mode 100644 index 000000000..c37251902 --- /dev/null +++ b/networkapi/plugins/Netconf/BGP/Cli.py @@ -0,0 +1,627 @@ +# -*- 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 os +from HTMLParser import HTMLParser + +from django.db.models import Q +from django.template import Context +from django.template import Template + +from networkapi.api_deploy import exceptions as deploy_exc +from networkapi.api_equipment.exceptions import \ + AllEquipmentsAreInMaintenanceException +from networkapi.api_neighbor.models import NeighborV4 +from networkapi.api_neighbor.models import NeighborV6 +from networkapi.equipamento import models as eqpt_models +from networkapi.extra_logging import local +from networkapi.extra_logging import NO_REQUEST_ID +from networkapi.infrastructure.ipaddr import IPAddress +from networkapi.settings import BGP_CONFIG_FILES_PATH +from networkapi.settings import BGP_CONFIG_TEMPLATE_PATH +from networkapi.settings import BGP_CONFIG_TOAPPLY_REL_PATH +from networkapi.settings import TFTPBOOT_FILES_PATH +from networkapi.plugins.Netconf.plugin import GenericNetconf + +log = logging.getLogger(__name__) + +class Generic(GenericNetconf): + + TEMPLATE_NEIGHBOR_V4_ADD = 'neighbor_v4_add' + TEMPLATE_NEIGHBOR_V4_REMOVE = 'neighbor_v4_remove' + TEMPLATE_NEIGHBOR_V6_ADD = 'neighbor_v6_add' + TEMPLATE_NEIGHBOR_V6_REMOVE = 'neighbor_v6_remove' + TEMPLATE_LIST_CONFIG_ADD = 'list_config_add' + TEMPLATE_LIST_CONFIG_REMOVE = 'list_config_remove' + TEMPLATE_ROUTE_MAP_ADD = 'route_map_add' + TEMPLATE_ROUTE_MAP_REMOVE = 'route_map_remove' + + def bgp(self): + return Generic(equipment=self.equipment) + + def _deploy_pre_req(self, neighbor): + log.info("_deploy_pre_req") + + # Concatenate RouteMapEntries Lists + route_map_in = neighbor.peer_group.route_map_in + route_map_out = neighbor.peer_group.route_map_out + + rms = route_map_in.route_map_entries | \ + route_map_out.route_map_entries + + for rm_entry in rms: + log.info(rm_entry) + list_config_bgp = rm_entry.list_config_bgp + # log.debug(dir(neighbor.peer_group)) + + # if not list_config_bgp.equipments.filter(id=self.equipment.id): + # log.info("Deploying list config BGP on equipment. Equipment ID: '%s'." % self.equipment.id) + # self.deploy_list_config_bgp(list_config_bgp) + + # # Deploying routemap In on equipment + # if not route_map_in.equipments.filter(id=self.equipment.id): + # log.info("Deploying routemap in on equipment. Equipment ID: '%s'" % self.equipment.id) + # self.deploy_route_map(neighbor.peer_group.route_map_in) + + # # Deploying routemap Out on equipment + # if not route_map_out.equipments.filter(id=self.equipment.id): + # log.info("Deploying routemap out on equipment. Equipment ID: '%s'" % self.equipment.id) + # self.deploy_route_map(neighbor.peer_group.route_map_out) + + + @staticmethod + def _get_type_list(type_): + log.info("Getting type list...") + + types = { + 'P': { + 'config_list': 'prefix-list', + 'route_map': 'ip address prefix-list' + }, + 'C': { + 'config_list': 'community', + 'route_map': '' + }, + } + + log.info(types[type_]) + return types[type_] + + @staticmethod + def _read_config(filename): + """ + Return the content from template_file + """ + + log.info("Reading config from file: '%s'" % filename) + + try: + file_handle = open(filename, 'r') + template_content = Template(file_handle.read()) + + log.info("Template content:") + log.info(template_content) + + file_handle.close() + log.info('File closed.') + + except IOError, e: + log.error('Error opening template file for read: %s' % filename) + raise Exception(e) + + except Exception, e: + log.error('Syntax error when parsing template: %s' % e) + raise Exception(e) + + return template_content + + @staticmethod + def _save_config(filename, config): + """ + Write config in template file + """ + + log.info("Saving config to file.") + log.info("Filename: %s" % filename) + log.info("Config: %s" % config) + + try: + file_handle = open(filename, 'w') + file_handle.write(config) + file_handle.close() + log.info("File closed.") + + except IOError, e: + log.error('Error writting to config gile: %s' % filename) + raise e + + @staticmethod + def _generate_template_dict_neighbor(neighbor): + """ + Make a dictionary to use in template + """ + + log.info("Generating a template dict to neighbor... Neighbor ID: '%s'" % neighbor.id) + + key_dict = { + 'AS_NUMBER': neighbor.local_asn.name, + 'LOCAL_IP': str(neighbor.local_ip), + 'VRF_NAME': neighbor.remote_ip.networkipv4.vlan.ambiente.default_vrf.internal_name, + 'REMOTE_IP': str(neighbor.remote_ip), + 'REMOTE_AS': neighbor.remote_asn.name, + 'ROUTE_MAP_IN': neighbor.peer_group.route_map_in.name, + 'ROUTE_MAP_OUT': neighbor.peer_group.route_map_out.name, + 'PASSWORD': neighbor.password, + 'TIMER_KEEPALIVE': neighbor.timer_keepalive, + 'TIMER_TIMEOUT': neighbor.timer_timeout, + 'DESCRIPTION': neighbor.description, + 'SOFT_RECONFIGURATION': neighbor.soft_reconfiguration, + 'NEXT_HOP_SELF': neighbor.next_hop_self, + 'REMOVE_PRIVATE_AS': neighbor.remove_private_as, + 'COMMUNITY': neighbor.community, + 'GROUP': 'GROUP_{}'.format(neighbor.remote_ip) + } + + log.info(key_dict) + + return key_dict + + def _generate_template_dict_route_map(self, route_map): + """ + Make a dictionary to use in template + """ + log.debug(dir(route_map)) + + log.info("Generate template dict for routemap. Routemap name: '%s'" % route_map.name) + + entries = [] + + for entry_obj in route_map.routemapentry_set.all(): + action = 'permit' if entry_obj.action == 'P' else 'deny' + entry = { + 'ACTION': action, + 'ORDER': entry_obj.order, + 'TYPE_MATCH': self._get_type_list( + entry_obj.list_config_bgp.type)['route_map'], + 'LIST': entry_obj.list_config_bgp.name, + 'ACTION_RECONFIG': entry_obj.action_reconfig + } + + entries.append(entry) + + key_dict = { + 'NAME': route_map.name, + 'ENTRIES': entries + } + + log.info(key_dict) + + return key_dict + + def _get_equipment_template(self, template_type): + """ + Return a script by equipment and template_type + """ + + log.info("Getting equipment's template... Template type: '%s'" % template_type) + + try: + return eqpt_models.EquipamentoRoteiro.search( + None, self.equipment.id, template_type + ).uniqueResult() + + except Exception as e: + log.error('Template type %s not found. Error: %s' %(template_type, e)) + raise self.exceptions.BGPTemplateException() + + def _load_template_file(self, template_type): + """ + Load template file with specific type related to equipment + + :template_type: Type of template to be loaded + + :returns: template string + """ + + log.info("Loading template file...") + + equipment_template = self._get_equipment_template(template_type=template_type) + log.info("Equipment's template: '%s'" % equipment_template) + + filename = BGP_CONFIG_TEMPLATE_PATH + '/' + equipment_template.roteiro.roteiro + log.info("Filename: '%s'" % filename) + + template_file = self._read_config(filename=filename) + log.info("Template file: '%s'" % template_file) + + return template_file + + def _get_template_config(self, template_type, config): + """ + Load template file and render values in VARs + """ + + log.info("Getting template config...") + + try: + template_file = self._load_template_file(template_type=template_type) + # Instancia o parser para desescapar entidades HTML + if config.get('CONFIG'): + converter = HTMLParser() + + # Copia o dicionário e faz o unescape apenas no campo 'CONFIG' + config['CONFIG'] = converter.unescape(config['CONFIG']) + + config_to_be_saved = template_file.render(Context(config)) + log.info(config_to_be_saved) + + except KeyError as err: + log.error('Error %s' % err) + raise deploy_exc.InvalidKeyException(err) + + except Exception as err: + log.error('Error: %s' % err) + raise self.exceptions.BGPTemplateException(err) + + return config_to_be_saved + + def _generate_config_file(self, types, template_type, config): + """ + Load a template and write a file with the rended output + + :returns: filename with relative path to settings.TFTPBOOT_FILES_PATH + """ + + log.info("Generating config file...") + + request_id = getattr(local, 'request_id', NO_REQUEST_ID) + + filename_out = 'bgp_{}_{}_config_{}'.format( + types, self.equipment.id, request_id + ) + + filename = BGP_CONFIG_FILES_PATH + filename_out + rel_file_to_deploy = BGP_CONFIG_TOAPPLY_REL_PATH + filename_out + + log.info("Filename: '%s'" % filename) + log.info("Relative file to deploy: '%s'" % rel_file_to_deploy) + + config = self._get_template_config(template_type=template_type, config=config) + log.info("Config generated successfully.") + + self._save_config(filename=filename, config=config) + log.info("Config saved successfully.") + + return rel_file_to_deploy + + def _apply_config(self, filename): + + log.info("_apply_config") + + if self.equipment.maintenance: + raise AllEquipmentsAreInMaintenanceException() + + self.copyScriptFileToConfig(filename=filename, destination='running-config') + log.info("Configuration in file: '%s' successfully applyied on equipment" % filename) + + def _deploy_config_in_equipment(self, rel_filename): + + log.info("Deploying config on equipment. Relative filename: '%s'." % rel_filename) + + path = os.path.abspath(TFTPBOOT_FILES_PATH + rel_filename) + + if not path.startswith(TFTPBOOT_FILES_PATH): + raise deploy_exc.InvalidFilenameException(rel_filename) + + return self._apply_config(filename=rel_filename) + + def _operate_equipment(self, types, template_type, config): + + log.info("Operate equipment method...") + + self.connect() + + file_to_deploy = self._generate_config_file( + types=types, template_type=template_type, config=config + ) + log.info("Configuration file generated successfully") + + self._deploy_config_in_equipment(file_to_deploy) + log.info("Configuration deployed on equipment successfully") + + self.close() + log.info("Connection closed successfully.") + + def _generate_template_dict_list_config_bgp(self, list_config_bgp): + """ + Make a dictionary to use in template + """ + + log.info("_generate_template_dict_list_config_bgp") + + key_dict = { + 'TYPE': self._get_type_list(list_config_bgp.type)['config_list'], + 'NAME': list_config_bgp.name, + 'CONFIG': list_config_bgp.config + } + + log.info(key_dict) + + return key_dict + + def _undeploy_pre_req(self, neighbor, ip_version): + + log.info("_undeploy_pre_req") + + # Concatenate RouteMapEntries Lists + route_map_in = neighbor.peer_group.route_map_in + route_map_out = neighbor.peer_group.route_map_out + + neighbors_v4 = NeighborV4.objects.filter(Q( + Q(peer_group__route_map_in=route_map_in) | + Q(peer_group__route_map_out=route_map_in) + )).filter(created=True) + + neighbors_v6 = NeighborV6.objects.filter(Q( + Q(peer_group__route_map_in=route_map_in) | + Q(peer_group__route_map_out=route_map_in) + )).filter(created=True) + + neighbors_v4 = neighbors_v4.exclude(Q(id=neighbor.id)) + neighbors_v6 = neighbors_v6.exclude(Q(id=neighbor.id)) + + log.info("Neighbors V4: '%s'." % neighbors_v4) + log.info("Neighbors V6: '%s'." % neighbors_v6) + + if not neighbors_v4 and not neighbors_v6: + log.info("No neighbors founded...") + try: + self.undeploy_route_map(route_map_in) + log.info("Routemap '%s' undeployed successfully." % route_map_in) + + except Exception as e: + log.error("Error while undeploying route-map. Error: {}".format(e)) + + neighbors_v4 = NeighborV4.objects.filter(Q( + Q(peer_group__route_map_in=route_map_out) | + Q(peer_group__route_map_out=route_map_out) + )).filter(created=True) + + neighbors_v6 = NeighborV6.objects.filter(Q( + Q(peer_group__route_map_in=route_map_out) | + Q(peer_group__route_map_out=route_map_out) + )).filter(created=True) + + neighbors_v4 = neighbors_v4.exclude(Q(id=neighbor.id)) + neighbors_v6 = neighbors_v6.exclude(Q(id=neighbor.id)) + + log.info("Neighbors V4: '%s'." % neighbors_v4) + log.info("Neighbors V6: '%s'." % neighbors_v6) + + if not neighbors_v4 and not neighbors_v6: + log.info("No neighbors founded...") + try: + self._undeploy_route_map(route_map=route_map_out) + log.info("Routemap '%s' undeployed successfully." % route_map_out) + + except Exception as e: + log.error("Error while undeploying route-map. Error: {}".format(e)) + + # List Config BGP + if not neighbors_v4 and not neighbors_v6: + rms = route_map_in.route_map_entries | \ + route_map_out.route_map_entries + + for rm_entry in rms: + list_config_bgp = rm_entry.list_config_bgp + log.info("List config BGP: '%s'." % list_config_bgp) + + neighbors_v4 = NeighborV4.objects.filter(Q( + Q(peer_group__route_map_in__routemapentry__list_config_bgp=list_config_bgp) | + Q(peer_group__route_map_out__routemapentry__list_config_bgp=list_config_bgp) + )).filter(created=True) + + neighbors_v6 = NeighborV6.objects.filter(Q( + Q(peer_group__route_map_in__routemapentry__list_config_bgp=list_config_bgp) | + Q(peer_group__route_map_out__routemapentry__list_config_bgp=list_config_bgp) + )).filter(created=True) + + neighbors_v4 = neighbors_v4.exclude(Q(id=neighbor.id)) + neighbors_v6 = neighbors_v6.exclue(Q(id=neighbor.id)) + + log.info("Neighbors V4: '%s'." % neighbors_v4) + log.info("Neighbors V6: '%s'." % neighbors_v6) + + if not neighbors_v4 and not neighbors_v6: + log.info("No neighbors founded...") + try: + self.undeploy_list_config_bgp(list_config_bgp=list_config_bgp) + log.info("List config BGP '%s' undeployed successfully." % list_config_bgp) + + except Exception as e: + log.error("Error while undeploying prefix-list. Error: {}".format(e)) + + def _operate(self, types, template_type, config): + + log.info("_operate method") + + file_to_deploy = self._generate_config_file( + types=types, + template_type=template_type, + config=config + ) + + log.info("File to Deploy: '%s'." % file_to_deploy) + + self._deploy_config_in_equipment(rel_filename=file_to_deploy) + log.info("Configuration successfully deployed on equipment.") + + self.close() + log.info("Netconf session closed.") + + def _undeploy_route_map(self, route_map): + """ + Undeploy route map + """ + + log.info("Undelpoy routemap method started... Routemap: '%s'" % route_map) + + config = self._generate_template_dict_route_map(route_map=route_map) + log.info("Configuration successfully generated.") + + self._operate( + types='route_map', + template_type=self.TEMPLATE_ROUTE_MAP_REMOVE, + config=config + ) + + def _open(self): + + log.info("Openning Netconf session...") + self.connect() + log.info("Session openned succesfully.") + + def undeploy_neighbor(self, neighbor): + """ + Undeploy neighbor + """ + + log.info("Undeploy neighbor method started... Neighbor: '%s'" % neighbor) + + ip_version = IPAddress(str(neighbor.remote_ip)).version + + template_type = self.TEMPLATE_NEIGHBOR_V4_REMOVE \ + if ip_version == 4 else self.TEMPLATE_NEIGHBOR_V6_REMOVE + + log.info("Template type: '%s'." % template_type) + + config = self._generate_template_dict_neighbor(neighbor=neighbor) + log.info("Configuration dict generated successfuly. Config: '%s'" % config) + + self._open() + self._operate( + types='neighbor', + template_type=template_type, + config=config + ) + + self._undeploy_pre_req( + neighbor=neighbor, + ip_version=ip_version + ) + log.info("Pre req undeployed successfully.") + + self.close() + + def deploy_list_config_bgp(self, list_config_bgp): + """ + Deploy prefix list + """ + + log.info("Deploy list config BGP method started... List Config BGP: '%s'." % list_config_bgp) + + config = self._generate_template_dict_list_config_bgp(list_config_bgp=list_config_bgp) + log.info("Dict list config BGP generated successfully. Config: '%s'" % config) + + self._operate_equipment( + types='list_config_bgp', + template_type=self.TEMPLATE_LIST_CONFIG_ADD, + config=config + ) + log.info("List config BGP successfully deployed.") + + def deploy_route_map(self, route_map): + """ + Deploy route map + """ + + log.info("Deploy route map method started...") + + config = self._generate_template_dict_route_map(route_map) + log.info("Dict config for routemap created successfully... Config: '%s'" % config) + + self._operate_equipment( + 'route_map', self.TEMPLATE_ROUTE_MAP_ADD, config + ) + log.info("Routemap successfully deployed.") + + def undeploy_list_config_bgp(self, list_config_bgp): + """ + Undeploy prefix list + """ + + log.info("Undeploy list config BGP method started.") + + config = self._generate_template_dict_list_config_bgp(list_config_bgp=list_config_bgp) + log.info("Dict for list config BGP created. Config: '%s'" % config) + + self._operate_equipment( + types='list_config_bgp', + template_type=self.TEMPLATE_LIST_CONFIG_REMOVE, + config=config + ) + log.info("List config BGP successfully undeployed.") + + def deploy_neighbor(self, neighbor): + """ + Deploy neighbor + """ + + log.info("Deploy neighbor method started...") + + self._deploy_pre_req(neighbor=neighbor) + log.info("Pre req successfully deployed...") + + ip_version = IPAddress(str(neighbor.remote_ip)).version + + template_type = self.TEMPLATE_NEIGHBOR_V4_ADD if ip_version == 4 else \ + self.TEMPLATE_NEIGHBOR_V6_ADD + log.info("Template type: '%s'." % template_type) + + config = self._generate_template_dict_neighbor(neighbor=neighbor) + log.info("Dict for neighbor config successfully generated. Config: '%s'" % config) + + self._operate_equipment( + types='neighbor', + template_type=template_type, + config=config + ) + log.info("Neighbor deployed successfully.") + + def undeploy_route_map(self, route_map): + """ + Undeply route map + """ + + log.info("Undeploy routemap method started...") + + config = self._generate_template_dict_route_map(route_map=route_map) + log.info("Dict for routemap config generated successfully. Config: '%s'" % config) + + self._operate( + types='route_map', + template_type=self.TEMPLATE_ROUTE_MAP_REMOVE, + config=config + ) + log.info("Routemap deployed successfully.") + + def _close(self): + log.info("Close connection method started") + self.close() + log.info("Connection closed successfully.") + diff --git a/networkapi/plugins/Netconf/BGP/__init__.py b/networkapi/plugins/Netconf/BGP/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/networkapi/plugins/Netconf/__init__.py b/networkapi/plugins/Netconf/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/networkapi/plugins/Netconf/plugin.py b/networkapi/plugins/Netconf/plugin.py new file mode 100644 index 000000000..425c1193d --- /dev/null +++ b/networkapi/plugins/Netconf/plugin.py @@ -0,0 +1,228 @@ +from ncclient import manager +from networkapi.plugins import exceptions +from networkapi.equipamento.models import EquipamentoAcesso +from networkapi.plugins.base import BasePlugin +import logging +from networkapi.system.facade import get_value +import paramiko +import requests, json, os +from django.db.utils import DatabaseError +from networkapi.system.exceptions import VariableDoesNotExistException + + + +log = logging.getLogger(__name__) + +class GenericNetconf(BasePlugin): + session_manager = None + alternative_variable_base_path_list = ['path_to_tftpboot'] + alternative_static_base_path_list = ['/mnt/scripts/tftpboot/'] + + + def __try_lock(self): + """ + Try lock to equipment, not necessary in Ncclient + """ + return True + + def connect(self): + """ + + Connects to equipment using NCClient to provide Netconf access + + :raises: + IOError: If can't connect to host + Exception: to any other unhandled exceptions + :return: None + """ + + log.info("Connection to equipment method started") + + + + ### If equipment access was not provided, then search the access ### + if self.equipment_access is None: + try: + log.info("Searching for equipment access...") + self.equipment_access = EquipamentoAcesso.search( + None, self.equipment, 'netconf').uniqueResult() + except Exception: + ### Equipment access not found + log.error('Access type %s not found for equipment %s.' % + ('netconf', self.equipment.nome)) + raise exceptions.InvalidEquipmentAccessException() + ### End block ### + + # Bypassing connection due python version incompatibility + try: + pass + + ### Exception handler + except IOError, e: + log.error('Could not connect to host %s: %s' % (self.equipment_access.fqdn, e)) + raise exceptions.ConnectionException(self.equipment_access.fqdn) + except Exception, e: + log.error('Error connecting to host %s:%s' % (self.equipment_access.fqdn, e)) + + def apply_config_to_equipment(self, config, use_vrf=None, tarqet='running'): + + log.info("Starting method to apply config on equipment... Config: '%s'" % config) + try: + if use_vrf is None: + use_vrf = self.management_vrf + + log.info("Config to apply: '%s'" % config) + + response = self.connect.edit_config(tarqet=tarqet, config=config) + log.info(response) + + return response + + except Exception, e: + log.error('Error on apply config to equipment') + + def ensure_privilege_level(self, privilege_level=None): + """ + 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) + """ + return True + + def check_configuration_has_content(self, command, file_path): + pass + + 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 as 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 + + message = "An error occurred while finding configuration file." + log.error("{} Could not find in: relative path ('{}'), system variables ({}) or static paths ({})".format( + message, file_path, self.alternative_variable_base_path_list, self.alternative_static_base_path_list)) + raise exceptions.APIException(message) + + 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 + But in this case, we will not copy a file. We will use the file to read the configuration and apply to + equipment by Netconf + + :param str filename: must contain the full path and file name + :param str use_vrf: not used + :param str destination: not used + + :returns: + Returns a success message, otherwise, raise an exception. That means will NOT return a false result. + """ + + 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) + log.info("Configuration for file '%s' is valid." % file_path) + + try: + command_file = open(file_path, "r") + command = command_file.read() + + return self.exec_command(command=command) + + except IOError as e: + self.close() + message = "Configuration file not found." # Message to client + log.error("{} {}: {}".format(message, file_path, e)) # Message to log + raise exceptions.APIException(message) + + def exec_command(self, command, success_regex='', invalid_regex=None, error_regex=None): + """ + Execute a command in equipment by netconf + """ + + log.info("Trying to execute a configuration on host {}...".format(self.equipment_access.fqdn)) + + try: + self.__try_lock() # Do nothing, will be executed by the locked method of ncclient + # Here we make a request to a microservice that runs in docker with python 3 + + ultimate_template = '{}'.format(command) + + response = requests.post( + url="http://localhost:5000/deploy", + headers={"Content-type": "application/json"}, + data=json.dumps({ + "address": self.equipment_access.fqdn, + "username": self.equipment_access.user, + "password": self.equipment_access.password, + "configuration": ultimate_template + }) + ) + + if response.status_code != 200: + raise Exception + + result_message = "Configuration was executed successfully on {}.".format(self.equipment_access.fqdn) + log.info(result_message) + return result_message + + except Exception as e: + message = "Error while excute netconf command on equipment %s" % self.equipment_access.fqdn + log.error(message) + log.error(e) + + raise exceptions.APIException(message) + + def close(self): + """ + Disconnect session from the equipment + + :returns: True if success or raise an exception on any error + """ + # Not necessary + pass + diff --git a/networkapi/plugins/factory.py b/networkapi/plugins/factory.py index a6ab32e1e..69ae94422 100644 --- a/networkapi/plugins/factory.py +++ b/networkapi/plugins/factory.py @@ -15,6 +15,7 @@ # limitations under the License. import logging import re +from networkapi.equipamento.models import Equipamento, EquipamentoAcesso from networkapi.plugins.SDN.ODL.Generic import ODLPlugin @@ -34,7 +35,15 @@ def plugin_exists(cls, **kwargs): @classmethod def get_plugin(cls, **kwargs): + if 'tipo_acesso' in kwargs and kwargs.get('tipo_acesso') == 'netconf': + if 'bgp' in kwargs: + bgp = kwargs.get('bgp') + if bgp: + from .Netconf.BGP.Cli import Generic + return Generic + from .Netconf.plugin import GenericNetconf + return GenericNetconf if 'modelo' in kwargs: modelo = kwargs.get('modelo') @@ -61,9 +70,9 @@ def get_plugin(cls, **kwargs): if 'marca' in kwargs: marca = kwargs.get('marca') - if re.search('HUAWEI', marca.upper(), re.DOTALL): - from .Huawei import Generic - return Generic + # if re.search('HUAWEI', marca.upper(), re.DOTALL): + # from .Netconf.plugin import GenericNetconf + # return GenericNetconf if re.search('F5', marca.upper(), re.DOTALL): from .F5.Generic import Generic return Generic @@ -106,7 +115,14 @@ def factory(cls, equipment, **kwargs): marca = equipment.modelo.marca.nome modelo = equipment.modelo.nome - plugin_class = cls.get_plugin(modelo=modelo, marca=marca, **kwargs) + tipo_acesso = EquipamentoAcesso.search(None, + equipment, + ).uniqueResult() + + if tipo_acesso is None: + return 'Equipment has no Access.' + + plugin_class = cls.get_plugin(modelo=modelo, marca=marca, tipo_acesso=tipo_acesso.tipo_acesso.protocolo, **kwargs) if type(plugin_class) == type(ODLPlugin): version = 'BERYLLIUM' diff --git a/networkapi/rack/resource/GeraConfig.py b/networkapi/rack/resource/GeraConfig.py index efb456c0f..a50533556 100644 --- a/networkapi/rack/resource/GeraConfig.py +++ b/networkapi/rack/resource/GeraConfig.py @@ -952,12 +952,20 @@ def autoprovision_splf(rack,FILEINLF1, FILEINLF2,FILEINSP1, FILEINSP2, FILEINSP3 variablestochangespine1['VLANBORDALEAF'] = str(VLANBORDALEAF[rack][0]) variablestochangespine1['VLANBORDACACHOSLEAF'] = str( VLANBORDACACHOSLEAF[rack][0]) + + ### TO POP Berrini + variablestochangespine1['IPNEIGHLEAFIPV4_2'] = str(IPLEAFipv4[rack][1]) + variablestochangespine1['IPNEIGHLEAFIPV6_2'] = str(IPLEAFipv6[rack][1]) + variablestochangespine1['LEAFNAME_2'] = HOSTNAME_LF2 + + variablestochangespine1['ASLEAF'] = str(ASLEAF[rack][0]) variablestochangespine1['IPNEIGHLEAFIPV4'] = str(IPLEAFipv4[rack][0]) variablestochangespine1['IPNEIGHLEAFIPV6'] = str(IPLEAFipv6[rack][0]) variablestochangespine1['INTERFACE'] = INTERFACE_SP1 variablestochangespine1['LEAFNAME'] = HOSTNAME_LF1 variablestochangespine1['INT_LF_UPLINK'] = int_lf1_sp1 + variablestochangespine1['RACK_NUM'] = str(rack) # # variablestochangespine2['IPSPINEIPV4'] = str(IPSPINEipv4[rack][1]) @@ -967,12 +975,22 @@ def autoprovision_splf(rack,FILEINLF1, FILEINLF2,FILEINSP1, FILEINSP2, FILEINSP3 variablestochangespine2['VLANBORDALEAF'] = str(VLANBORDALEAF[rack][1]) variablestochangespine2['VLANBORDACACHOSLEAF'] = str( VLANBORDACACHOSLEAF[rack][1]) + + + ### TO POP Berrini + variablestochangespine1['IPNEIGHLEAFIPV4_2'] = str(IPLEAFipv4[rack][0]) + variablestochangespine1['IPNEIGHLEAFIPV6_2'] = str(IPLEAFipv6[rack][0]) + variablestochangespine1['LEAFNAME_2'] = HOSTNAME_LF2 + + variablestochangespine2['ASLEAF'] = str(ASLEAF[rack][0]) variablestochangespine2['IPNEIGHLEAFIPV4'] = str(IPLEAFipv4[rack][1]) variablestochangespine2['IPNEIGHLEAFIPV6'] = str(IPLEAFipv6[rack][1]) variablestochangespine2['INTERFACE'] = INTERFACE_SP2 variablestochangespine2['LEAFNAME'] = HOSTNAME_LF1 variablestochangespine2['INT_LF_UPLINK'] = int_lf1_sp2 + variablestochangespine2['RACK_NUM'] = str(rack) + # # variablestochangespine3['IPSPINEIPV4'] = str(IPSPINEipv4[rack][2]) @@ -988,6 +1006,8 @@ def autoprovision_splf(rack,FILEINLF1, FILEINLF2,FILEINSP1, FILEINSP2, FILEINSP3 variablestochangespine3['INTERFACE'] = INTERFACE_SP3 variablestochangespine3['LEAFNAME'] = HOSTNAME_LF2 variablestochangespine3['INT_LF_UPLINK'] = int_lf2_sp3 + variablestochangespine3['RACK_NUM'] = str(rack) + # # variablestochangespine4['IPSPINEIPV4'] = str(IPSPINEipv4[rack][3]) @@ -1003,6 +1023,8 @@ def autoprovision_splf(rack,FILEINLF1, FILEINLF2,FILEINSP1, FILEINSP2, FILEINSP3 variablestochangespine4['INTERFACE'] = INTERFACE_SP4 variablestochangespine4['LEAFNAME'] = HOSTNAME_LF2 variablestochangespine4['INT_LF_UPLINK'] = int_lf2_sp4 + variablestochangespine4['RACK_NUM'] = str(rack) + # # variablestochangeleaf1['IPLEAFSP1IPV4'] = str(IPLEAFipv4[rack][0]) diff --git a/networkapi/settings.py b/networkapi/settings.py index 9e30d6775..fbd44242d 100644 --- a/networkapi/settings.py +++ b/networkapi/settings.py @@ -71,7 +71,7 @@ def local_files(path): NETWORKAPI_DATABASE_PORT = os.getenv('NETWORKAPI_DATABASE_PORT', '3306') NETWORKAPI_DATABASE_OPTIONS = os.getenv( 'NETWORKAPI_DATABASE_OPTIONS', - '{"init_command": "SET storage_engine=INNODB"}') + '{"init_command": "SET default_storage_engine=INNODB"}') # Configurações de banco de dados DATABASES = { diff --git a/networkapi/util/interface_validate.py b/networkapi/util/interface_validate.py new file mode 100644 index 000000000..d045681a3 --- /dev/null +++ b/networkapi/util/interface_validate.py @@ -0,0 +1,87 @@ +""" +Interface validator module +""" +import re +import logging +from networkapi.api_rest.exceptions import NetworkAPIException + +log = logging.getLogger(__name__) + + +class InterfaceOverrideValidator: + """ + Class responsible to interfaces (or ports) valitions. + """ + + def check_overriding(self, source_interface_str_list, target_interface_str_list): + """ + Public method to check if source interface overides target interfaces. + + Allowed interfaces sample: + source_interface_str_list = ['eth1/1', 'eth3/2'] + target_interface_str_list = ['eth2', 'eth3/1'] + + Prohibited interfaces sample: + source_interface_str_list = ['eth1/1', 'eth3/2'] + target_interface_str_list = ['eth1', 'eth1/1', 'eth3'] + + :param source_interface_str_list str list: String array of interfaces, ex.: ['eth1', 'eth2', 'eth2/1'] + :param target_interface_str_list str list: String array of interfaces, ex.: ['eth2/1/3', 'eth2/1/2/1'] + :return first prohibited interface as False, otherwise, allowed interface as True: + """ + + try: + + log.info('check_overriding START') + log.info('source_interface_str_list: %s', source_interface_str_list) + log.info('target_interface_str_list: %s', target_interface_str_list) + + # Validate + for source_interface_str in source_interface_str_list: + for target_interface_str in target_interface_str_list: + + log.info("Validating '%s' with '%s'", source_interface_str, source_interface_str) + source_interface_array = [int(num) for num in re.findall(r'\d+', source_interface_str)] + target_interface_array = [int(num) for num in re.findall(r'\d+', target_interface_str)] + response = self._is_overriding( + source_list=source_interface_array, + target_list=target_interface_array) + if not response: + raise NetworkAPIException("A interface requisitada '{}' sobrepoe a interface '{}' do equipamento".format( + source_interface_str, target_interface_str + ) + ) + + except Exception as ex: + raise NetworkAPIException(str(ex)) + + def _is_overriding(self, source_list, target_list, lvl=0): + """ + Private method check if source interface overides target interfaces. + The interfaces are represented by array, ex. [1,1] is 'eth1/1'. + + :param source interfaces: Represented array of interfaces, ex.: [1,1] [1,2,1] [2] + :param source interfaces: Represented array of interfaces, ex.: [1] [1,2,3] [4] + :param recursive level control. + :return first prohibited interface as False, otherwise, allowed interface as True: + """ + + try: + # Identical + if source_list == target_list: + log.info('*** PROHIBITED ***') + return False + elif not source_list or not target_list: + log.info('**** PROHIBITED ****') + return False + elif source_list and target_list and source_list[0] == target_list[0]: + return self._is_overriding( + source_list=source_list[1:], + target_list=target_list[1:], + lvl=lvl+1 + ) + else: + log.info('**** ALLOWED ****') + return True + except Exception as ex: + raise NetworkAPIException(str(ex)) diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile index 5a98a61ef..4bbf628e1 100644 --- a/scripts/docker/Dockerfile +++ b/scripts/docker/Dockerfile @@ -1,7 +1,7 @@ # # Base stage # -FROM alpine:3.7 as base +FROM alpine:3.7 as base RUN apk update diff --git a/scripts/docker/docker-start-netapi.sh b/scripts/docker/docker-start-netapi.sh index 3cc12301f..ed37b82c2 100755 --- a/scripts/docker/docker-start-netapi.sh +++ b/scripts/docker/docker-start-netapi.sh @@ -53,7 +53,7 @@ fi echo "Creating networkapi database" -mysql -u root -h ${NETWORKAPI_DATABASE_HOST} -e 'CREATE DATABASE IF NOT EXISTS networkapi;' +mysql -u root -h ${NETWORKAPI_DATABASE_HOST} -p${NETWORKAPI_DATABASE_PASSWORD} -e 'CREATE DATABASE IF NOT EXISTS networkapi;' # Running database migrations if exists @@ -61,7 +61,7 @@ cd /netapi/dbmigrate; db-migrate --show-sql echo "Loading base Network API data into database" -mysql -u root -h ${NETWORKAPI_DATABASE_HOST} networkapi < /netapi/dev/load_example_environment.sql +mysql -u root -h ${NETWORKAPI_DATABASE_HOST} -p${NETWORKAPI_DATABASE_PASSWORD} networkapi < /netapi/dev/load_example_environment.sql # Discovering SDN controller diff --git a/scripts/docker/netapi.env b/scripts/docker/netapi.env index 9b9849506..2f79e793f 100644 --- a/scripts/docker/netapi.env +++ b/scripts/docker/netapi.env @@ -1,10 +1,10 @@ -NETWORKAPI_DATABASE_NAME=networkapi +NETWORKAPI_DATABASE_NAME='networkapi' -NETWORKAPI_DATABASE_USER=root +NETWORKAPI_DATABASE_USER='root' -NETWORKAPI_DATABASE_PASSWORD= +NETWORKAPI_DATABASE_PASSWORD='put_your_password_here' -NETWORKAPI_DATABASE_HOST=netapi_db +NETWORKAPI_DATABASE_HOST='netapi_db' NETWORKAPI_DATABASE_PORT=3306