-
Robert Latta authoredRobert Latta authored
ims_data.py 15.97 KiB
import logging
import re
from collections import defaultdict
from copy import copy
from itertools import chain
from inventory_provider import environment
from inventory_provider.db import ims
from inventory_provider.db.ims import InventoryStatus, IMS, \
CUSTOMER_RELATED_CONTACT_PROPERTIES, EXTRA_FIELD_VALUE_PROPERTIES
environment.setup_logging()
logger = logging.getLogger(__name__)
# Dashboard V3
IMS_OPSDB_STATUS_MAP = {
InventoryStatus.PLANNED: 'planned',
InventoryStatus.READY_FOR_SERVICE: 'installed',
InventoryStatus.IN_SERVICE: 'operational',
InventoryStatus.MIGRATION: 'planned',
InventoryStatus.OUT_OF_SERVICE: 'terminated',
InventoryStatus.READY_FOR_CEASURE: 'terminated'
}
STATUSES_TO_IGNORE = \
[InventoryStatus.OUT_OF_SERVICE.value]
_POP_LOCATION_SCHEMA_STRUCT = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'city': {'type': 'string'},
'country': {'type': 'string'},
'abbreviation': {'type': 'string'},
'longitude': {'type': 'number'},
'latitude': {'type': 'number'}
},
'required': [
'name',
'city',
'country',
'abbreviation',
'longitude',
'latitude'],
'additionalProperties': False
}
POP_LOCATION_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
**_POP_LOCATION_SCHEMA_STRUCT
}
NODE_LOCATION_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'definitions': {
'pop-location': _POP_LOCATION_SCHEMA_STRUCT
},
'type': 'object',
'properties': {
'equipment-name': {'type': 'string'},
'status': {'type': 'string'},
'pop': {'$ref': '#/definitions/pop-location'}
},
'required': ['equipment-name', 'status', 'pop'],
'additionalProperties': False
}
def get_non_monitored_circuit_ids(ds: IMS):
# note the id for the relevant field is hard-coded. I didn't want to use
# the name of the field as this can be changed by users
for d in ds.get_filtered_entities(
'ExtraFieldValue',
'extrafield.id == 2898 | value == 0',
EXTRA_FIELD_VALUE_PROPERTIES['ExtraFieldValueObjectInfo']
):
yield d['extrafieldvalueobjectinfo']['objectid']
def get_monitored_circuit_ids(ds: IMS):
# note the id for the relevant field is hard-coded. I didn't want to use
# the name of the field as this can be changed by users
for d in ds.get_filtered_entities(
'ExtraFieldValue',
'extrafield.id == 2898 | value == 1',
EXTRA_FIELD_VALUE_PROPERTIES['ExtraFieldValueObjectInfo'],
step_count=10000
):
yield d['extrafieldvalueobjectinfo']['objectid']
def get_ids_and_sids(ds: IMS):
for sid_circuit in ds.get_filtered_entities(
'ExtraFieldValue',
'extrafieldid == 3209 | value <> ""',
step_count=10000
):
yield sid_circuit['objectid'], sid_circuit['value']
def get_service_types(ds: IMS):
for d in ds.get_filtered_entities(
'ComboBoxData',
'name == "PW_INFORM_PRODUCTCODE"'):
yield d['selection']
def get_customer_service_emails(ds: IMS):
customer_contacts = defaultdict(set)
for x in ds.get_filtered_entities(
'customerrelatedcontact',
"contact.plannedworkmail != ''",
CUSTOMER_RELATED_CONTACT_PROPERTIES['Contact']
):
customer_contacts[x['customerid']].add(x['contact']['mail'])
for x in ds.get_filtered_entities(
'customerrelatedcontact',
"contact.troubleticketMail != ''",
CUSTOMER_RELATED_CONTACT_PROPERTIES['Contact']
):
customer_contacts[x['customerid']].add(x['contact']['mail'])
for k, v in customer_contacts.items():
yield k, sorted(list(v))
def get_circuit_related_customers(ds: IMS):
return_value = defaultdict(list)
for ccr in ds.get_filtered_entities(
'CircuitCustomerRelation',
'circuit.inventoryStatusId== 3',
ims.CIRCUIT_CUSTOMER_RELATION['Customer']):
return_value[ccr['circuitid']].append(
{
'id': ccr['customer']['id'],
'name': ccr['customer']['name'],
'type': ccr['customer']['customertypeid']
}
)
return return_value
def get_port_id_services(ds: IMS):
circuit_nav_props = [
ims.CIRCUIT_PROPERTIES['Ports'],
ims.CIRCUIT_PROPERTIES['InternalPorts'],
]
ims_service_names = list(get_service_types(ds))
customers = {c['id']: c['name'] for c in ds.get_all_entities('customer')}
products = {p['id']: p['name'] for p in ds.get_all_entities('product')}
def _get_circuit_type(c):
if products[c['productid']] in ims_service_names:
return 'service'
else:
return 'circuit'
def _get_circuits():
_ignore_status_str = ' | '.join([
f'inventoryStatusId != {s}' for s in STATUSES_TO_IGNORE
])
for c in ds.get_filtered_entities(
'Circuit',
_ignore_status_str,
circuit_nav_props,
step_count=2000):
c['circuit_type'] = _get_circuit_type(c)
yield c
circuits = _get_circuits()
# order of preference
# internalports (first and last sequencenumbers)
# ports (first and last sequencenumbers)
# internal port a / internal port b
# port a / port b
# if there are more than two ports we'll yield a circuit of the ends of the
# sequence and the reverse; and then single ended circuits for all ports
# between
# e.g. four ports are reported [a,b,c,d] the 4 circs would have endpoints
# a and d
# d and a
# b only
# c only
def _populate_end_info(_circuit, _port_ids):
port_ids = [p for p in _port_ids if p]
if not port_ids:
return []
port_a_id = port_ids[0]
port_b_id = port_ids[-1]
_circuit['port_a_id'] = port_a_id
if port_a_id != port_b_id:
_circuit['port_b_id'] = port_b_id
yield copy(_circuit)
_circuit['port_a_id'], _circuit['port_b_id'] = \
_circuit['port_b_id'], _circuit['port_a_id']
yield copy(_circuit)
if len(port_ids) > 2:
_circuit.pop('port_b_id', None)
for p in port_ids[1:-1]:
_circuit['port_a_id'] = p
yield copy(_circuit)
for circuit in circuits:
cd = {
'id': circuit['id'],
'name': circuit['name'],
'status': IMS_OPSDB_STATUS_MAP.get(
InventoryStatus(circuit['inventorystatusid']), 'unknown'),
'circuit_type': circuit['circuit_type'],
'service_type': products[circuit['productid']],
'project': customers[circuit['customerid']],
'customer': customers[circuit['customerid']],
'customerid': circuit['customerid']
}
ports = []
cd['port_type'] = 'unknowm'
if circuit['internalports']:
ports = sorted(
circuit['internalports'], key=lambda x: x['sequencenumber'])
ports = [p['id'] for p in ports]
cd['port_type'] = 'internal'
elif circuit['ports']:
ports = sorted(
circuit['ports'], key=lambda x: x['sequencenumber'])
ports = [p['id'] for p in ports]
cd['port_type'] = 'ports'
elif circuit['portaid'] or circuit['portbid']:
ports = [circuit['portaid'], circuit['portbid']]
cd['port_type'] = 'ab'
yield from _populate_end_info(cd, ports)
ignore_status_str = ''.join([
f'circuit.inventoryStatusId != {s} | ' for s in STATUSES_TO_IGNORE
])
for portrelate in chain(
ds.get_filtered_entities(
'vmportrelate',
ignore_status_str +
'circuitId != 0',
ims.VM_PORT_RELATE_PROPERTIES['Circuit'],
step_count=2000
),
ds.get_filtered_entities(
'vminternalportrelate',
ignore_status_str +
'circuitId != 0',
ims.VM_INTERNAL_PORT_RELATE_PROPERTIES['Circuit'],
step_count=2000
)
):
circuit = portrelate['circuit']
if circuit:
yield {
'id': circuit['id'],
'name': circuit['name'],
'status': IMS_OPSDB_STATUS_MAP.get(
InventoryStatus(circuit['inventorystatusid']),
'unknown'),
'circuit_type': _get_circuit_type(circuit),
'service_type': products[circuit['productid']],
'project': customers[circuit['customerid']],
'customer': customers[circuit['customerid']],
'customerid': circuit['customerid'],
'port_a_id': portrelate.get(
'portid',
portrelate.get('internalportid', '')),
'port_type': 'port relate'
}
def get_port_details(ds: IMS):
port_nav_props = [
ims.PORT_PROPERTIES['Node'],
ims.PORT_PROPERTIES['Shelf']
]
internal_port_nav_props = {
ims.INTERNAL_PORT_PROPERTIES['Node'],
ims.PORT_PROPERTIES['Shelf']
}
# this is here instead of chaining to make debugging easier
def _process_ports(ports, p_type):
for p in ports:
vendor = None
interface_name = None
try:
vendor = \
p['node']['equipmentdefinition']['vendor']['name'].lower()
except (TypeError, KeyError):
pass
# if there become more exceptions we will need to abstract this
if vendor == 'infinera' and p.get('shelf', None):
try:
interface_name = \
f"{p['shelf']['sequencenumber']}-{p['name']}"
except KeyError:
pass
if not interface_name:
interface_name = p['name']
data = {
'port_id': p['id'],
'equipment_name': p['node']['name'],
'interface_name': interface_name
}
yield data
yield from _process_ports(ds.get_all_entities(
'port', port_nav_props, step_count=2000), 'external')
yield from _process_ports(ds.get_all_entities(
'internalport', internal_port_nav_props, step_count=2000), 'internal')
def get_circuit_hierarchy(ds: IMS):
circuit_nav_props = [
ims.CIRCUIT_PROPERTIES['Customer'],
ims.CIRCUIT_PROPERTIES['Product'],
ims.CIRCUIT_PROPERTIES['Speed'],
ims.CIRCUIT_PROPERTIES['SubCircuits'],
ims.CIRCUIT_PROPERTIES['CarrierCircuits']
]
ignore_status_str = ' | '.join([
f'inventoryStatusId != {s}' for s in STATUSES_TO_IGNORE
])
circuits = ds.get_filtered_entities(
'Circuit',
ignore_status_str,
circuit_nav_props,
step_count=1000)
# circuits = ds.get_all_entities(
# 'Circuit', circuit_nav_props, step_count=1000)
service_types = list(get_service_types(ds))
for circuit in circuits:
if circuit['product']['name'] in service_types:
circuit_type = 'service'
else:
circuit_type = 'circuit'
sub_circuits = [c['subcircuitid'] for c in circuit['subcircuits']]
carrier_circuits = \
[c['carriercircuitid'] for c in circuit['carriercircuits']]
yield {
'id': circuit['id'],
'name': circuit['name'],
'status': IMS_OPSDB_STATUS_MAP.get(
InventoryStatus(circuit['inventorystatusid']), 'unknown'),
'product': circuit['product']['name'],
'speed': circuit['speed']['name'],
'project': circuit['customer']['name'],
'circuit-type': circuit_type,
'sub-circuits': sub_circuits,
'carrier-circuits': carrier_circuits,
'customerid': circuit['customerid']
}
def get_node_locations(ds: IMS):
"""
return location info for all Site nodes
yields dictionaries formatted as:
.. as_json::
inventory_provider.db.ims_data.NODE_LOCATION_SCHEMA
:param ds:
:return: yields dicts as above
"""
site_nav_props = [
ims.SITE_PROPERTIES['City'],
ims.SITE_PROPERTIES['SiteAliases'],
ims.SITE_PROPERTIES['Country'],
ims.SITE_PROPERTIES['Nodes']
]
sites = ds.get_all_entities('Site', site_nav_props, step_count=500)
for site in sites:
city = site['city']
abbreviation = ''
try:
abbreviation = site['sitealiases'][0]['aliasname']
except IndexError:
pass # no alias - ignore silently
for node in site['nodes']:
if node['inventorystatusid'] in STATUSES_TO_IGNORE:
continue
yield (node['name'], {
'equipment-name': node['name'],
'status': IMS_OPSDB_STATUS_MAP.get(
InventoryStatus(node['inventorystatusid']), 'unknown'),
'pop': {
'name': site['name'],
'city': city['name'],
'country': city['country']['name'],
'abbreviation': abbreviation,
'longitude': site['longitude'],
'latitude': site['latitude'],
}
})
yield ('UNKNOWN_LOC', {
'equipment-name': 'UNKNOWN',
'status': 'unknown',
'pop': {
'name': 'UNKNOWN',
'city': 'UNKNOWN',
'country': 'UNKNOWN',
'abbreviation': 'UNKNOWN',
'longitude': 0,
'latitude': 0,
}
})
# End of Dashboard V3 stuff
INTERNAL_POP_NAMES = {
'Cambridge OC',
'DANTE Lab',
'GÉANT LAB',
'GEANT LAB',
'Amsterdam GEANT Office',
'Amsterdam GÉANT Office',
'CAMBRIDGE CITY HOUSE',
'AMSTERDAM GEANT OFFICE',
}
def lookup_lg_routers(ds: IMS):
pattern = re.compile("vpn-proxy|vrr|taas", re.IGNORECASE)
def _matching_node(node_):
if InventoryStatus(node_['inventorystatusid']) in STATUSES_TO_IGNORE:
return False
if pattern.match(node_['name']):
return False
return True
site_nav_props = [
ims.SITE_PROPERTIES['SiteAliases'],
ims.SITE_PROPERTIES['City'],
ims.SITE_PROPERTIES['Country']
]
eq_definitions = ds.get_filtered_entities(
'EquipmentDefinition',
'Name like MX',
ims.EQUIP_DEF_PROPERTIES['Nodes'])
for eq_def in eq_definitions:
nodes = eq_def['nodes']
for node in nodes:
if not _matching_node(node):
continue
if node['inventorystatusid'] in STATUSES_TO_IGNORE:
continue
site = ds.get_entity_by_id('Site', node['siteid'], site_nav_props,
True)
city = site['city']
abbreviation = ''
try:
abbreviation = site['sitealiases'][0]['aliasname']
except IndexError:
pass # no alias - ignore silently
eq = {
'equipment name': node['name'],
'type':
'INTERNAL'
if site['name'] in INTERNAL_POP_NAMES
else 'CORE',
'pop': {
'name': site['name'],
'city': city['name'],
'country': city['country']['name'],
'country code': city['country']['abbreviation'],
'abbreviation': abbreviation,
'longitude': site['longitude'],
'latitude': site['latitude'],
}
}
yield eq
def lookup_geant_nodes(ds: IMS):
return (n["name"]for n in ds.get_filtered_entities(
'Node',
'customer.Name == "GEANT"',
ims.EQUIP_DEF_PROPERTIES['Nodes']))