blob: 8e336421e3820141585e7999db9b9b1b2c01b6d3 [file] [log] [blame]
# Copyright 2017-present Open Networking Foundation
#
# Licensed 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.
from xossynchronizer.pull_steps.pullstep import PullStep
from xossynchronizer.modelaccessor import model_accessor, OLTDevice, VOLTService, PONPort, NNIPort
from xosconfig import Config
from multistructlog import create_logger
import requests
from requests import ConnectionError
from requests.models import InvalidURL
import os, sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from helpers import Helpers
log = create_logger(Config().get('logging'))
class OLTDevicePullStep(PullStep):
def __init__(self, model_accessor):
super(OLTDevicePullStep, self).__init__(model_accessor=model_accessor, observed_model=OLTDevice)
@staticmethod
def get_ids_from_logical_device(o):
voltha_url = Helpers.get_voltha_info(o.volt_service)['url']
voltha_port = Helpers.get_voltha_info(o.volt_service)['port']
r = requests.get("%s:%s/api/v1/logical_devices" % (voltha_url, voltha_port), timeout=1)
if r.status_code != 200:
raise Exception("Failed to retrieve logical devices from VOLTHA: %s" % r.text)
res = r.json()
for ld in res["items"]:
if ld["root_device_id"] == o.device_id:
o.of_id = ld["id"]
o.dp_id = "of:" + Helpers.datapath_id_to_hex(ld["datapath_id"]) # convert to hex
# Note: If the device is administratively disabled, then it's likely we won't find a logical device for
# it. Only throw the exception for OLTs that are enabled.
if not o.of_id and not o.dp_id and o.admin_state == "ENABLED":
raise Exception("Can't find a logical device for device id: %s" % o.device_id)
def pull_records(self):
log.debug("[OLT pull step] pulling OLT devices from VOLTHA")
try:
self.volt_service = VOLTService.objects.all()[0]
except IndexError:
log.warn('VOLTService not found')
return
voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
try:
r = requests.get("%s:%s/api/v1/devices" % (voltha_url, voltha_port), timeout=1)
if r.status_code != 200:
log.debug("[OLT pull step] It was not possible to fetch devices from VOLTHA")
# [SEBA-367] Handling blank response received from Voltha, Scenario occurs when voltha api is called while vcore service is re-starting
elif r.text:
# keeping only OLTs
devices = [d for d in r.json()["items"] if "olt" in d["type"]]
log.debug("[OLT pull step] received devices", olts=devices)
olts_in_voltha = self.create_or_update_olts(devices)
self.delete_olts(olts_in_voltha)
else:
log.debug("[OLT pull step] Blank response received")
except (ValueError, TypeError), e:
log.warn("[OLT pull step] Invalid Json received in response from VOLTHA", reason=e)
return
except ConnectionError, e:
log.warn("[OLT pull step] It was not possible to connect to VOLTHA", reason=e)
return
except InvalidURL, e:
log.warn("[OLT pull step] VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
return
def create_or_update_olts(self, olts):
updated_olts = []
for olt in olts:
if olt["type"] == "simulated_olt":
[host, port] = ["172.17.0.1", "50060"]
elif "host_and_port" in olt:
[host, port] = olt["host_and_port"].split(":")
elif "mac_address" in olt:
mac_address = olt["mac_address"]
olt_ports = self.fetch_olt_ports(olt["id"])
try:
if "host_and_port" in olt:
model = OLTDevice.objects.filter(device_type=olt["type"], host=host, port=port)[0]
log.debug("[OLT pull step] OLTDevice already exists, updating it", device_type=olt["type"], id=model.id, host=host, port=port)
elif "mac_address" in olt:
model = OLTDevice.objects.filter(device_type=olt["type"], mac_address=mac_address)[0]
log.debug("[OLT pull step] OLTDevice already exists, updating it", device_type=olt["type"], id=model.id, mac_address=mac_address)
if model.enacted < model.updated:
log.debug("[OLT pull step] Skipping pull on OLTDevice %s as enacted < updated" % model.name, name=model.name, id=model.id, enacted=model.enacted, updated=model.updated)
# if we are not updating the device we still need to pull ports
if olt_ports:
self.create_or_update_ports(olt_ports, model)
updated_olts.append(model)
continue
except IndexError:
model = OLTDevice()
model.device_type = olt["type"]
if olt["type"] == "simulated_olt":
model.host = "172.17.0.1"
model.port = 50060
log.debug("[OLT pull step] OLTDevice is new, creating it. Simulated")
elif "host_and_port" in olt:
[host, port] = olt["host_and_port"].split(":")
model.host = host
model.port = int(port)
log.debug("[OLT pull step] OLTDevice is new, creating it", device_type=olt["type"], host=host, port=port)
elif "mac_address" in olt:
model.mac_address = olt["mac_address"]
log.debug("[OLT pull step] OLTDevice is new, creating it", device_type=olt["type"], mac_address=mac_address)
# there's no name in voltha, so make one up based on the id
model.name = "OLT-%s" % olt["id"]
nni_ports = [p for p in olt_ports if "ETHERNET_NNI" in p["type"]]
if not nni_ports:
log.warning("[OLT pull step] No NNI ports, so no way to determine uplink. Skipping.", device_type=olt["type"], host=host, port=port)
model.device_id = olt["id"] # device_id must be populated for delete_olts
updated_olts.append(model)
continue
# Exctract uplink from the first NNI port. This decision is arbitrary, we will worry about multiple
# NNI ports when that situation arises.
model.uplink = str(nni_ports[0]["port_no"])
# Initial admin_state
model.admin_state = olt["admin_state"]
# Check to see if Voltha's serial_number field is populated. During Activation it's possible that
# Voltha's serial_number field may be blank. We want to avoid overwriting a populated data model
# serial number with an unpopulated Voltha serial number. IF this happened, then there would be
# a window of vulnerability where sadis requests will fail.
if olt['serial_number'] and model.serial_number != olt['serial_number']:
# Check to see if data model serial number is already populated. If the serial number was
# already populated, and what we just learned from Voltha differs, then this is an error
# that should be made visible to the operator, so the operator may correct it.
if model.serial_number:
log.error("Serial number mismatch when pulling olt. Aborting OLT Update.",
model_serial_number=model.serial_number,
voltha_serial_number=olt['serial_number'],
olt_id=model.id)
model.backend_status = "Incorrect serial number"
model.backend_code = 2
model.save_changed_fields()
# Have to include this in the result, or delete_olts() will delete it
updated_olts.append(model)
# Stop processing this OLT
continue
# Preserve existing behavior.
# Data model serial number is unpopulated, so populate it.
# TODO(smbaker): Consider making serial_number a required field, then do away with this.
# Deferred until after SEBA-2.0 release.
log.info("Pull step learned olt serial number from voltha",
model_serial_number=model.serial_number,
voltha_serial_number=olt['serial_number'],
olt_id=model.id)
model.serial_number = olt['serial_number']
# Adding feedback state to the device
model.device_id = olt["id"]
model.oper_status = olt["oper_status"]
model.volt_service = self.volt_service
model.volt_service_id = self.volt_service.id
# get logical device
OLTDevicePullStep.get_ids_from_logical_device(model)
model.save_changed_fields()
if olt_ports:
self.create_or_update_ports(olt_ports, model)
updated_olts.append(model)
return updated_olts
def fetch_olt_ports(self, olt_device_id):
""" Given an olt device_id, query voltha for the set of ports associated with that OLT.
Returns a list of port dictionaries, or None in case of error.
"""
voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
try:
r = requests.get("%s:%s/api/v1/devices/%s/ports" % (voltha_url, voltha_port, olt_device_id), timeout=1)
if r.status_code != 200:
log.warn("[OLT pull step] It was not possible to fetch ports from VOLTHA for device %s" % olt_device_id,
status_code=r.status_code)
return None
ports = r.json()['items']
log.debug("[OLT pull step] received ports", ports=ports, olt=olt_device_id)
return ports
except ConnectionError, e:
log.warn("[OLT pull step] It was not possible to connect to VOLTHA", reason=e)
return None
except InvalidURL, e:
log.warn("[OLT pull step] VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
return None
return None
def create_or_update_ports(self, ports, olt):
nni_ports = [p for p in ports if "ETHERNET_NNI" in p["type"]]
pon_ports = [p for p in ports if "PON_OLT" in p["type"]]
self.create_or_update_nni_port(nni_ports, olt)
self.create_or_update_pon_port(pon_ports, olt)
def create_or_update_pon_port(self, pon_ports, olt):
update_ports = []
for port in pon_ports:
try:
model = PONPort.objects.filter(port_no=port["port_no"], olt_device_id=olt.id)[0]
log.debug("[OLT pull step] PONPort already exists, updating it", port_no=port["port_no"], olt_device_id=olt.id)
except IndexError:
model = PONPort()
model.port_no = port["port_no"]
model.olt_device_id = olt.id
model.name = port["label"]
log.debug("[OLT pull step] PONPort is new, creating it", port_no=port["port_no"], olt_device_id=olt.id)
model.admin_state = port["admin_state"]
model.oper_status = port["oper_status"]
model.save_changed_fields()
update_ports.append(model)
return update_ports
def create_or_update_nni_port(self, nni_ports, olt):
update_ports = []
for port in nni_ports:
try:
model = NNIPort.objects.filter(port_no=port["port_no"], olt_device_id=olt.id)[0]
model.xos_managed = False
log.debug("[OLT pull step] NNIPort already exists, updating it", port_no=port["port_no"], olt_device_id=olt.id)
except IndexError:
model = NNIPort()
model.port_no = port["port_no"]
model.olt_device_id = olt.id
model.name = port["label"]
model.xos_managed = False
log.debug("[OLT pull step] NNIPort is new, creating it", port_no=port["port_no"], olt_device_id=olt.id)
model.admin_state = port["admin_state"]
model.oper_status = port["oper_status"]
model.save_changed_fields()
update_ports.append(model)
return update_ports
def delete_olts(self, olts_in_voltha):
olts_id_in_voltha = [m.device_id for m in olts_in_voltha]
xos_olts = OLTDevice.objects.all()
deleted_in_voltha = [o for o in xos_olts if o.device_id not in olts_id_in_voltha]
for model in deleted_in_voltha:
if model.enacted < model.updated:
# DO NOT delete a model that is being processed
log.debug("[OLT pull step] device is not present in VOLTHA, skipping deletion as sync is in progress", device_id=o.device_id,
name=o.name)
continue
log.debug("[OLT pull step] deleting device as it's not present in VOLTHA", device_id=o.device_id, name=o.name, id=o.id)
model.delete()