netconf client support and disable-enable support
Change-Id: Idd9bbdd15f59783abf3c70745d3a00e00177687e
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index fe8370a..e61eb8d 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -16,15 +16,18 @@
"""
Adtran generic VOLTHA device handler
"""
+import argparse
import datetime
import pprint
+import shlex
+import time
import arrow
-import re
import structlog
from twisted.internet import reactor, defer
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
+from voltha.adapters.adtran_olt.net.adtran_netconf import AdtranNetconfClient
from voltha.adapters.adtran_olt.net.adtran_rest import AdtranRestClient
from voltha.protos import third_party
from voltha.protos.common_pb2 import OperStatus, AdminState, ConnectStatus
@@ -36,6 +39,8 @@
OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
from voltha.registry import registry
+from common.utils.asleep import asleep
+
_ = third_party
@@ -68,6 +73,9 @@
# HTTP shortcuts
HELLO_URI = '/restconf/adtran-hello:hello'
+ # RPC XML shortcuts
+ RESTART_RPC = '<system-restart xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>'
+
def __init__(self, adapter, device_id, username='', password='', timeout=20):
self.adapter = adapter
self.adapter_agent = adapter.adapter_agent
@@ -82,30 +90,45 @@
# Northbound and Southbound ports
self.northbound_ports = {} # port number -> Port
self.southbound_ports = {} # port number -> Port (For PON, use pon-id as key)
- self.management_ports = {} # port number -> Port TODO: Not currently supported
+ # self.management_ports = {} # port number -> Port TODO: Not currently supported
self.num_northbound_ports = None
self.num_southbound_ports = None
- self.num_management_ports = None
+ # self.num_management_ports = None
+
+ self.ip_address = None
+ self.timeout = timeout
+ self.restart_failure_timeout = 5 * 60 # 5 Minute timeout
# REST Client
- self.ip_address = None
self.rest_port = None
- self.rest_timeout = timeout
self.rest_username = username
self.rest_password = password
self.rest_client = None
+ # NETCONF Client
+ self.netconf_port = None
+ self.netconf_username = username
+ self.netconf_password = password
+ self.netconf_client = None
+
# Heartbeat support
self.heartbeat_count = 0
self.heartbeat_miss = 0
- self.heartbeat_interval = 10 # TODO: Decrease before release
+ self.heartbeat_interval = 10 # TODO: Decrease before release or any scale testing
self.heartbeat_failed_limit = 3
self.heartbeat_timeout = 5
self.heartbeat = None
self.heartbeat_last_reason = ''
- self.max_ports = 1 # TODO: Remove later
+ # Virtualized OLT Support
+ self.is_virtual_olt = False
+
+ # Installed flows
+ self.flow_entries = {} # Flow ID/name -> FlowEntry
+
+ # TODO Remove items below after one PON fully supported and working as expected
+ self.max_ports = 1
def __del__(self):
# Kill any startup or heartbeat defers
@@ -114,65 +137,108 @@
if d is not None:
d.cancel()
+ ldi, self.logical_device_id = self.logical_device_id, None
+
h, self.heartbeat = self.heartbeat, None
if h is not None:
h.cancel()
+ # Remove the logical device
+
+ if ldi is not None:
+ logical_device = self.adapter_agent.get_logical_device(ldi)
+ self.adapter_agent.delete_logical_device(logical_device)
+
self.northbound_ports.clear()
self.southbound_ports.clear()
def __str__(self):
- return "AdtranDeviceHandler: {}:{}".format(self.ip_address, self.rest_port)
+ return "AdtranDeviceHandler: {}".format(self.ip_address)
+
+ def parse_provisioning_options(self, device):
+ if not device.ipv4_address:
+ self.activate_failed(device, 'No ip_address field provided')
+
+ self.ip_address = device.ipv4_address
+
+ #############################################################
+ # Now optional parameters
+
+ def check_tcp_port(value):
+ ivalue = int(value)
+ if ivalue <= 0 or ivalue > 65535:
+ raise argparse.ArgumentTypeError("%s is a not a valid port number" % value)
+ return ivalue
+
+ parser = argparse.ArgumentParser(description='Adtran Device Adapter')
+ parser.add_argument('--nc_username', '-u', action='store', default='hsvroot', help='NETCONF username')
+ parser.add_argument('--nc_password', '-p', action='store', default='BOSCO', help='NETCONF Password')
+ parser.add_argument('--nc_port', '-t', action='store', default=830, type=check_tcp_port,
+ help='NETCONF TCP Port')
+ parser.add_argument('--rc_username', '-U', action='store', default='ADMIN', help='REST username')
+ parser.add_argument('--rc_password', '-P', action='store', default='PASSWORD', help='REST Password')
+ parser.add_argument('--rc_port', '-T', action='store', default=8081, type=check_tcp_port,
+ help='REST TCP Port')
+
+ try:
+ args = parser.parse_args(shlex.split(device.extra_args))
+
+ self.netconf_username = args.nc_username
+ self.netconf_password = args.nc_password
+ self.netconf_port = args.nc_port
+
+ self.rest_username = args.rc_username
+ self.rest_password = args.rc_password
+ self.rest_port = args.rc_port
+
+ except argparse.ArgumentError as e:
+ self.activate_failed(device,
+ 'Invalid arguments: {}'.format(e.message),
+ reachable=False)
+ except Exception as e:
+ self.log.exception('parsing error: {}'.format(e.message))
@inlineCallbacks
- def activate(self, device):
+ def activate(self, device, reconciling=False):
"""
Activate the OLT device
:param device: A voltha.Device object, with possible device-type
- specific extensions.
+ specific extensions.
+ :param reconciling: If True, this adapter is taking over for a previous adapter
+ for an existing OLT
"""
- self.log.info('AdtranDeviceHandler.activating', device=device)
+ self.log.info('AdtranDeviceHandler.activating', device=device, reconciling=reconciling)
if self.logical_device_id is None:
- if not device.host_and_port:
- self.activate_failed(device, 'No host_and_port field provided')
-
- pattern = '(?P<host>[^:/ ]+).?(?P<port>[0-9]*).*'
- info = re.match(pattern, device.host_and_port)
-
- if not info or len(info.group('host')) == 0 or len(info.group('port')) == 0 or \
- (int(info.group('port')) if info.group('port') else None) is None:
- self.activate_failed(device, 'Invalid Host or Port provided',
- reachable=False)
-
- self.ip_address = str(info.group('host'))
- self.rest_port = int(info.group('port'))
+ # Parse our command line options for this device
+ self.parse_provisioning_options(device)
############################################################################
# Start initial discovery of RESTCONF support (if any)
- self.rest_client = AdtranRestClient(self.ip_address,
- self.rest_port,
- self.rest_username,
- self.rest_password,
- self.rest_timeout)
+
try:
- # content: (dict) Modules from the hello message
-
- self.startup = self.rest_client.request('GET', self.HELLO_URI, name='hello')
-
+ self.startup = self.make_restconf_connection()
results = yield self.startup
self.log.debug('HELLO Contents: {}'.format(pprint.PrettyPrinter().pformat(results)))
+ # See if this is a virtualized OLT. If so, no NETCONF support available
+
+ self.is_virtual_olt = 'module-info' in results and\
+ any(mod.get('module-name', None) == 'adtran-ont-mock'
+ for mod in results['module-info'])
+ if self.is_virtual_olt:
+ self.log.info('*** VIRTUAL OLT detected ***')
+
except Exception as e:
- results = None
self.log.exception('Initial RESTCONF adtran-hello failed', e=e)
self.activate_failed(device, e.message, reachable=False)
############################################################################
- # TODO: Get these six via NETCONF and from the derived class
+ # Start initial discovery of NETCONF support (if any)
+<<<<<<< HEAD
device.model = 'TODO: Adtran PizzaBox, YUM'
device.hardware_version = 'TODO: H/W Version'
device.firmware_version = 'TODO: S/W Version'
@@ -180,11 +246,42 @@
Image(version="TODO: S/W Version")
])
device.serial_number = 'TODO: Serial Number'
+=======
+ if not self.is_virtual_olt:
+ try:
+ self.startup = self.make_netconf_connection()
+ yield self.startup
+>>>>>>> c577acb... netconf client support and disable-enable support
- device.root = True
- device.vendor = 'Adtran, Inc.'
- device.connect_status = ConnectStatus.REACHABLE
- self.adapter_agent.update_device(device)
+ except Exception as e:
+ self.log.exception('Initial NETCONF connection failed', e=e)
+ self.activate_failed(device, e.message, reachable=False)
+
+ ############################################################################
+ # Get the device Information
+
+ if reconciling:
+ device.connect_status = ConnectStatus.REACHABLE
+ self.adapter_agent.update_device(device)
+ else:
+ try:
+ self.startup = self.get_device_info(device)
+ results = yield self.startup
+
+ device.model = results.get('model', 'unknown')
+ device.hardware_version = results.get('hardware_version', 'unknown')
+ device.firmware_version = results.get('firmware_version', 'unknown')
+ device.software_version = results.get('software_version', 'unknown')
+ device.serial_number = results.get('serial_number', 'unknown')
+
+ device.root = True
+ device.vendor = results.get('vendor', 'Adtran, Inc.')
+ device.connect_status = ConnectStatus.REACHABLE
+ self.adapter_agent.update_device(device)
+
+ except Exception as e:
+ self.log.exception('Device Information request(s) failed', e=e)
+ self.activate_failed(device, e.message, reachable=False)
try:
# Enumerate and create Northbound NNI interfaces
@@ -195,8 +292,9 @@
self.startup = self.process_northbound_ports(device, results)
yield self.startup
- for port in self.northbound_ports.itervalues():
- self.adapter_agent.add_port(device.id, port.get_port())
+ if not reconciling:
+ for port in self.northbound_ports.itervalues():
+ self.adapter_agent.add_port(device.id, port.get_port())
except Exception as e:
self.log.exception('Northbound port enumeration and creation failed', e=e)
@@ -212,13 +310,15 @@
self.startup = self.process_southbound_ports(device, results)
yield self.startup
- for port in self.southbound_ports.itervalues():
- self.adapter_agent.add_port(device.id, port.get_port())
+ if not reconciling:
+ for port in self.southbound_ports.itervalues():
+ self.adapter_agent.add_port(device.id, port.get_port())
except Exception as e:
self.log.exception('Southbound port enumeration and creation failed', e=e)
self.activate_failed(device, e.message)
+<<<<<<< HEAD
# Complete activation by setting up logical device for this OLT and saving
# off the devices parent_id
@@ -273,25 +373,31 @@
for port in self.northbound_ports.itervalues():
self.startup = port.start()
yield self.startup
+=======
+ if reconciling:
+ if device.admin_state == AdminState.ENABLED:
+ if device.parent_id:
+ self.logical_device_id = device.parent_id
+ self.adapter_agent.reconcile_logical_device(device.parent_id)
+ else:
+ self.log.info('no-logical-device-set')
+>>>>>>> c577acb... netconf client support and disable-enable support
- except Exception as e:
- self.log.exception('Failed to start northbound port(s)', e=e)
- self.activate_failed(device, e.message)
+ # Reconcile child devices
+ self.adapter_agent.reconcile_child_devices(device.id)
+ else:
+ # Complete activation by setting up logical device for this OLT and saving
+ # off the devices parent_id
- try:
- start_downlinks = self.initial_port_state == AdminState.ENABLED
+ self.logical_device_id = self.create_logical_device(device)
- for port in self.southbound_ports.itervalues():
- self.startup = port.start() if start_downlinks else port.stop()
- yield self.startup
+ # Create logical ports for all southbound and northbound interfaces
- except Exception as e:
- self.log.exception('Failed to start southbound port(s)', e=e)
- self.activate_failed(device, e.message)
+ self.create_logical_ports(device, self.logical_device_id, reconciling)
# Complete device specific steps
try:
- self.startup = self.complete_device_specific_activation(device, results)
+ self.startup = self.complete_device_specific_activation(device, reconciling)
if self.startup is not None:
yield self.startup
@@ -303,12 +409,8 @@
self.start_heartbeat(delay=10)
- # Save off logical ID and specify that we active
-
- self.logical_device_id = ld_initialized.id
-
device = self.adapter_agent.get_device(device.id)
- device.parent_id = ld_initialized.id
+ device.parent_id = self.logical_device_id
device.oper_status = OperStatus.ACTIVE
self.adapter_agent.update_device(device)
@@ -331,6 +433,127 @@
self.adapter_agent.update_device(device)
raise RuntimeError('Failed to activate OLT: {}'.format(device.reason))
+ def make_netconf_connection(self, connect_timeout=None):
+ ############################################################################
+ # Start initial discovery of NETCONF support
+
+ if self.netconf_client is None:
+ self.netconf_client = AdtranNetconfClient(self.ip_address,
+ self.netconf_port,
+ self.netconf_username,
+ self.netconf_password,
+ self.timeout)
+ if self.netconf_client.connected:
+ return defer.returnValue(True)
+
+ timeout = connect_timeout or self.timeout
+ return self.netconf_client.connect(timeout)
+
+ def make_restconf_connection(self, get_timeout=None):
+ if self.rest_client is None:
+ self.rest_client = AdtranRestClient(self.ip_address,
+ self.rest_port,
+ self.rest_username,
+ self.rest_password,
+ self.timeout)
+
+ timeout = get_timeout or self.timeout
+ return self.rest_client.request('GET', self.HELLO_URI, name='hello', timeout=timeout)
+
+ def create_logical_device(self, device):
+ ld = LogicalDevice(
+ # NOTE: not setting id and datapath_id will let the adapter agent pick id
+ desc=ofp_desc(mfr_desc=device.vendor,
+ hw_desc=device.hardware_version,
+ sw_desc=device.software_version,
+ serial_num=device.serial_number,
+ dp_desc='n/a'),
+ switch_features=ofp_switch_features(n_buffers=256, # TODO fake for now
+ n_tables=2, # TODO ditto
+ capabilities=(
+ # OFPC_FLOW_STATS | # TODO: Enable if we support it
+ # OFPC_TABLE_STATS | # TODO: Enable if we support it
+ # OFPC_GROUP_STATS | # TODO: Enable if we support it
+ OFPC_PORT_STATS)),
+ root_device_id=device.id)
+
+ ld_initialized = self.adapter_agent.create_logical_device(ld)
+
+ return ld_initialized
+
+ @inlineCallbacks
+ def create_logical_ports(self, device, ld_initialized, reconciling):
+
+ if not reconciling:
+ for port in self.northbound_ports.itervalues():
+ lp = port.get_logical_port()
+ if lp is not None:
+ self.adapter_agent.add_logical_port(ld_initialized.id, lp)
+
+ for port in self.southbound_ports.itervalues():
+ lp = port.get_logical_port()
+ if lp is not None:
+ self.adapter_agent.add_logical_port(ld_initialized.id, lp)
+
+ # Set the ports in a known good initial state
+ try:
+ for port in self.northbound_ports.itervalues():
+ self.startup = port.reset()
+ results = yield self.startup
+ self.log.debug('Northbound Port reset results', results=results)
+
+ except Exception as e:
+ self.log.exception('Failed to reset northbound ports to known good initial state', e=e)
+ self.activate_failed(device, e.message)
+
+ try:
+ for port in self.southbound_ports.itervalues():
+ self.startup = port.reset()
+ results = yield self.startup
+ self.log.debug('Southbound Port reset results', results=results)
+
+ except Exception as e:
+ self.log.exception('Failed to reset southbound ports to known good initial state', e=e)
+ self.activate_failed(device, e.message)
+
+ # Start/stop the interfaces as needed
+ try:
+ for port in self.northbound_ports.itervalues():
+ self.startup = port.start()
+ results = yield self.startup
+ self.log.debug('Northbound Port start results', results=results)
+
+ except Exception as e:
+ self.log.exception('Failed to start northbound port(s)', e=e)
+ self.activate_failed(device, e.message)
+
+ try:
+ if reconciling:
+ start_downlinks = device.admin_state == AdminState.ENABLED
+ else:
+ start_downlinks = self.initial_port_state == AdminState.ENABLED
+
+ for port in self.southbound_ports.itervalues():
+ self.startup = port.start() if start_downlinks else port.stop()
+ results = yield self.startup
+ self.log.debug('Southbound Port start results', results=results)
+
+ except Exception as e:
+ self.log.exception('Failed to start southbound port(s)', e=e)
+ self.activate_failed(device, e.message)
+
+ @inlineCallbacks
+ def device_information(self, device):
+ """
+ Examine the various managment models and extract device information for
+ VOLTHA use
+
+ :param device: A voltha.Device object, with possible device-type
+ specific extensions.
+ :return: (Deferred or None).
+ """
+ yield defer.Deferred(lambda c: c.callback("Not Required"))
+
@inlineCallbacks
def enumerate_northbound_ports(self, device):
"""
@@ -396,7 +619,7 @@
yield defer.Deferred(lambda c: c.callback("Not Required"))
@inlineCallbacks
- def complete_device_specific_activation(self, _device, _content):
+ def complete_device_specific_activation(self, _device, _reconciling):
return None
def deactivate(self, device):
@@ -409,6 +632,342 @@
if h is not None:
h.cancel()
+ # TODO: What else (delete logical device, ???)
+
+ @inlineCallbacks
+ def disable(self):
+ """
+ This is called when a previously enabled device needs to be disabled based on a NBI call.
+ """
+ self.log.info('disabling', device_id=self.device_id)
+
+ # Get the latest device reference
+ device = self.adapter_agent.get_device(self.device_id)
+
+ # Suspend any active healthchecks / pings
+
+ h, self.heartbeat = self.heartbeat, None
+
+ if h is not None:
+ h.cancel()
+
+ # Update the operational status to UNKNOWN
+
+ device.oper_status = OperStatus.UNKNOWN
+ device.connect_status = ConnectStatus.UNREACHABLE
+ self.adapter_agent.update_device(device)
+
+ # Remove the logical device
+ ldi, self.logical_device_id = self.logical_device_id, None
+
+ if ldi is not None:
+ logical_device = self.adapter_agent.get_logical_device(ldi)
+ self.adapter_agent.delete_logical_device(logical_device)
+
+ # Disable all child devices first
+ self.adapter_agent.update_child_devices_state(self.device_id,
+ admin_state=AdminState.DISABLED)
+
+ # Remove the peer references from this device
+ self.adapter_agent.delete_all_peer_references(self.device_id)
+
+ # Set all ports to disabled
+ self.adapter_agent.disable_all_ports(self.device_id)
+
+ for port in self.northbound_ports.itervalues():
+ port.stop()
+
+ for port in self.southbound_ports.itervalues():
+ port.stop()
+
+ # Disable all flows TODO: Do we want to delete them?
+ # TODO: Use bulk methods if possible
+
+ for flow in self.flow_entries.itervalues():
+ flow.disable()
+
+ # Shutdown communications with OLT
+
+ if self.netconf_client is not None:
+ try:
+ yield self.netconf_client.close()
+ except Exception as e:
+ self.log.exception('NETCONF client shutdown failed', e=e)
+
+ def _null_clients():
+ self.netconf_client = None
+ self.rest_client = None
+
+ reactor.callLater(0, _null_clients)
+
+ self.log.info('disabled', device_id=device.id)
+
+ @inlineCallbacks
+ def reenable(self):
+ """
+ This is called when a previously disabled device needs to be enabled based on a NBI call.
+ """
+ self.log.info('re-enabling', device_id=self.device_id)
+
+ # Get the latest device reference
+ device = self.adapter_agent.get_device(self.device_id)
+
+ # Update the connect status to REACHABLE
+ device.connect_status = ConnectStatus.REACHABLE
+ self.adapter_agent.update_device(device)
+
+ # Set all ports to enabled
+ self.adapter_agent.enable_all_ports(self.device_id)
+
+ try:
+ yield self.make_restconf_connection()
+
+ except Exception as e:
+ self.log.exception('RESTCONF adtran-hello reconnect failed', e=e)
+ # TODO: What is best way to handle reenable failure?
+
+ if not self.is_virtual_olt:
+ try:
+ yield self.make_netconf_connection()
+
+ except Exception as e:
+ self.log.exception('NETCONF re-connection failed', e=e)
+ # TODO: What is best way to handle reenable failure?
+
+ # Recreate the logical device
+
+ ld_initialized = self.create_logical_device(device)
+
+ # Create logical ports for all southbound and northbound interfaces
+
+ self.create_logical_ports(device, ld_initialized, False)
+
+ device = self.adapter_agent.get_device(device.id)
+ device.parent_id = ld_initialized.id
+ device.oper_status = OperStatus.ACTIVE
+ self.adapter_agent.update_device(device)
+ self.logical_device_id = ld_initialized.id
+
+ # Reenable all child devices
+ self.adapter_agent.update_child_devices_state(device.id,
+ admin_state=AdminState.ENABLED)
+
+ for port in self.northbound_ports.itervalues():
+ port.start()
+
+ for port in self.southbound_ports.itervalues():
+ port.start()
+
+ # TODO:
+ # 1) Restart health check / pings
+
+ # Enable all flows
+ # TODO: Use bulk methods if possible
+
+ for flow in self.flow_entries:
+ flow.enable()
+
+ self.log.info('re-enabled', device_id=device.id)
+
+ @inlineCallbacks
+ def reboot(self):
+ """
+ This is called to reboot a device based on a NBI call. The admin state of the device
+ will not change after the reboot.
+ """
+ self.log.debug('reboot')
+
+ # Update the operational status to ACTIVATING and connect status to
+ # UNREACHABLE
+
+ device = self.adapter_agent.get_device(self.device_id)
+ previous_oper_status = device.oper_status
+ previous_conn_status = device.connect_status
+ device.oper_status = OperStatus.ACTIVATING
+ device.connect_status = ConnectStatus.UNREACHABLE
+ self.adapter_agent.update_device(device)
+
+ # Update the child devices connect state to UNREACHABLE
+ self.adapter_agent.update_child_devices_state(self.device_id,
+ connect_status=ConnectStatus.UNREACHABLE)
+ # Issue reboot command
+
+ if not self.is_virtual_olt:
+ try:
+ yield self.netconf_client.rpc(AdtranDeviceHandler.RESTART_RPC)
+
+ except Exception as e:
+ self.log.exception('NETCONF client shutdown', e=e)
+ # TODO: On failure, what is the best thing to do?
+
+ # Shutdown communications with OLT. Typically it takes about 2 seconds
+ # or so after the reply before the restart actually occurs
+
+ try:
+ response = yield self.netconf_client.close()
+ self.log.debug('Restart response XML was: {}'.format('ok' if response.ok else 'bad'))
+
+ except Exception as e:
+ self.log.exception('NETCONF client shutdown', e=e)
+
+ def _null_clients():
+ self.netconf_client = None
+ self.rest_client = None
+
+ yield reactor.callLater(0, _null_clients)
+
+ # Run remainder of reboot process as a new task. The OLT then may be up in a
+ # few moments or may take 3 minutes or more depending on any self tests enabled
+
+ current_time = time.time();
+ timeout = current_time + self.restart_failure_timeout
+
+ self.log('*** Current time is {}, timeout is {}'.format(current_time, timeout))
+
+ yield reactor.callLater(10, self._finish_reboot, timeout,
+ previous_oper_status, previous_conn_status)
+
+ @inlineCallbacks
+ def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
+ # Now wait until REST & NETCONF are re-established or we timeout
+
+ if self.netconf_client is None and not self.is_virtual_olt:
+ self.log.debug('Attempting to restore NETCONF connection')
+ try:
+ response = yield self.make_netconf_connection(connect_timeout=3)
+ self.log.debug('Restart NETCONF connection XML was: {}'.format(response.xml))
+
+ except Exception as e:
+ self.log.debug('No NETCONF connection yet: {}'.format(e.message))
+ try:
+ yield self.netconf_client.close()
+ except Exception as e:
+ self.log.exception(e.message)
+ finally:
+ def _null_netconf():
+ self.log.debug('Nulling out the NETCONF client')
+ self.netconf_client = None
+ reactor.callLater(0, _null_netconf)
+
+ elif self.rest_client is None:
+ self.log.debug('Attempting to restore RESTCONF connection')
+ try:
+ response = yield self.make_restconf_connection(get_timeout=3)
+ self.log.debug('Restart RESTCONF connection XML was: {}'.format(response.xml))
+
+ except Exception:
+ self.log.debug('No RESTCONF connection yet')
+ self.rest_client = None
+
+ if (self.netconf_client is None and not self.is_virtual_olt) or self.rest_client is None:
+ current_time = time.time();
+
+ self.log('Current time is {}, timeout is {}'.format(current_time, timeout))
+
+ if current_time < timeout:
+ self.log.info('Device not responding yet, will try again...')
+ yield reactor.callLater(10, self._finish_reboot, timeout,
+ previous_oper_status, previous_conn_status)
+
+ if self.netconf_client is None and not self.is_virtual_olt:
+ self.log.error('Could not restore NETCONF communications after device RESET')
+ pass # TODO: What is best course of action if cannot get clients back?
+
+ if self.rest_client is None:
+ self.log.error('Could not restore RESTCONF communications after device RESET')
+ pass # TODO: What is best course of action if cannot get clients back?
+
+ # Pause additional 5 seconds to let things OLT microservices complete some more initialization
+
+ yield asleep(5)
+
+ # Get the latest device reference
+
+ device = self.adapter_agent.get_device(self.device_id)
+ device.oper_status = previous_oper_status
+ device.connect_status = previous_conn_status
+ self.adapter_agent.update_device(device)
+
+ # Update the child devices connect state to REACHABLE
+ self.adapter_agent.update_child_devices_state(self.device_id,
+ connect_status=ConnectStatus.REACHABLE)
+
+ # Connect back up to OLT so heartbeats/polls start working again
+ try:
+ yield self.make_restconf_connection()
+
+ except Exception as e:
+ self.log.exception('RESTCONF adtran-hello connect after reboot failed', e=e)
+ # TODO: What is best way to handle reenable failure?
+
+ if not self.is_virtual_olt:
+ try:
+ yield self.make_netconf_connection()
+
+ except Exception as e:
+ self.log.exception('NETCONF re-connection after reboot failed', e=e)
+ # TODO: What is best way to handle reenable failure?
+
+ self.log.info('rebooted', device_id=self.device_id)
+
+ @inlineCallbacks
+ def delete(self):
+ """
+ This is called to delete a device from the PON based on a NBI call.
+ If the device is an OLT then the whole PON will be deleted.
+ """
+ self.log.info('deleting', device_id=self.device_id)
+
+ # Cancel any outstanding tasks
+
+ d, self.startup = self.startup, None
+ if d is not None:
+ d.cancel()
+
+ h, self.heartbeat = self.heartbeat, None
+ if h is not None:
+ h.cancel()
+
+ # TODO:
+ # 1) Remove all flows from the device
+
+ self.flow_entries.clear()
+
+ # Remove all child devices
+ self.adapter_agent.delete_all_child_devices(self.device_id)
+
+ # Remove the logical device
+ logical_device = self.adapter_agent.get_logical_device(self.logical_device_id)
+ self.adapter_agent.delete_logical_device(logical_device)
+
+ # Remove the peer references from this device
+ self.adapter_agent.delete_all_peer_references(self.device_id)
+
+ # Tell all ports to stop any background processing
+
+ for port in self.northbound_ports.itervalues():
+ port.delete()
+
+ for port in self.southbound_ports.itervalues():
+ port.delete()
+
+ self.northbound_ports.clear()
+ self.southbound_ports.clear()
+
+ # Shutdown communications with OLT
+
+ if self.netconf_client is not None:
+ try:
+ yield self.netconf_client.close()
+ except Exception as e:
+ self.log.exception('NETCONF client shutdown', e=e)
+
+ self.netconf_client = None
+
+ self.rest_client = None
+
+ self.log.info('deleted', device_id=self.device_id)
+
@inlineCallbacks
def get_device_info(self, device):
"""
@@ -422,8 +981,15 @@
specific extensions. Such extensions shall be described as part of
the device type specification returned by device_types().
"""
- pass
- return None # raise NotImplementedError('TODO: You should override this in your derived class???')
+ device = {}
+ # device['model'] = 'TODO: Adtran PizzaBox, YUM'
+ # device['hardware_version'] = 'TODO: H/W Version'
+ # device['firmware_version'] = 'TODO: S/W Version'
+ # device['software_version'] = 'TODO: S/W Version'
+ # device['serial_number'] = 'TODO: Serial Number'
+ # device['vendor'] = 'Adtran, Inc.'
+
+ returnValue(device)
def start_heartbeat(self, delay=10):
assert delay > 1