blob: 087a9292fa7e69c5d6930c7067442c6de45c2e23 [file] [log] [blame]
# Copyright 2017-present Adtran, Inc.
#
# 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.
import structlog
import random
import time
from adtran_netconf import AdtranNetconfClient
from common.utils.asleep import asleep
from ncclient.operations.rpc import RPCReply, RPCError
from twisted.internet.defer import inlineCallbacks, returnValue
log = structlog.get_logger()
_dummy_xml = '<rpc-reply message-id="br-549" ' + \
'xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" ' + \
'xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">' + \
'<data/>' + \
'</rpc-reply>'
class MockNetconfClient(AdtranNetconfClient):
"""
Performs NETCONF requests
"""
def __init__(self, host_ip, port=830, username='', password='', timeout=20):
super(MockNetconfClient, self).__init__(host_ip, port=port, username=username,
password=password, timeout=timeout)
self._connected = False
self._locked = {}
def __str__(self):
return "MockNetconfClient {}@{}:{}".format(self._username, self._ip, self._port)
@property
def capabilities(self):
"""
Get the server's NETCONF capabilities
:return: (ncclient.capabilities.Capabilities) object representing the server's capabilities.
"""
return None
@property
def connected(self):
"""
Is this client connected to a NETCONF server
:return: (boolean) True if connected
"""
return self._connected
@inlineCallbacks
def connect(self, connect_timeout=None):
"""
Connect to the NETCONF server
o To disable attempting publickey authentication altogether, call with
allow_agent and look_for_keys as False.`
o hostkey_verify enables hostkey verification from ~/.ssh/known_hosts
:return: (deferred) Deferred request
"""
yield asleep(random.uniform(0.1, 5.0)) # Simulate NETCONF request delay
self._connected = True
self._locked = {}
returnValue(True)
@inlineCallbacks
def close(self):
"""
Close the connection to the NETCONF server
:return: (deferred) Deferred request
"""
yield asleep(random.uniform(0.1, 0.5)) # Simulate NETCONF request delay
self._connected = False
self._locked = {}
returnValue(True)
@inlineCallbacks
def get_config(self, source='running'):
"""
Get the configuration from the specified source
:param source: (string) Configuration source, 'running', 'candidate', ...
:return: (deferred) Deferred request that wraps the GetReply class
"""
yield asleep(random.uniform(0.1, 4.0)) # Simulate NETCONF request delay
# TODO: Customize if needed...
xml = _dummy_xml
returnValue(RPCReply(xml))
@inlineCallbacks
def get(self, payload):
"""
Get the requested data from the server
:param payload: Payload/filter
:return: (defeered) for GetReply
"""
yield asleep(random.uniform(0.1, 3.0)) # Simulate NETCONF request delay
# TODO: Customize if needed...
xml = _dummy_xml
returnValue(RPCReply(xml))
@inlineCallbacks
def lock(self, source, lock_timeout):
"""
Lock the configuration system
:param source: is the name of the configuration datastore accessed
:param lock_timeout: timeout in seconds for holding the lock
:return: (defeered) for RpcReply
"""
expire_time = time.time() + lock_timeout
if source not in self._locked:
self._locked[source] = None
while self._locked[source] is not None:
# Watch for lock timeout
if time.time() >= self._locked[source]:
self._locked[source] = None
break
yield asleep(0.1)
if time.time() < expire_time:
yield asleep(random.uniform(0.1, 0.5)) # Simulate NETCONF request delay
self._locked[source] = expire_time
returnValue(RPCReply(_dummy_xml) if expire_time > time.time() else RPCError('TODO'))
@inlineCallbacks
def unlock(self, source):
"""
Get the requested data from the server
:param rpc_string: RPC request
:param source: is the name of the configuration datastore accessed
:return: (defeered) for RpcReply
"""
if source not in self._locked:
self._locked[source] = None
if self._locked[source] is not None:
yield asleep(random.uniform(0.1, 0.5)) # Simulate NETCONF request delay
self._locked[source] = None
returnValue(RPCReply(_dummy_xml))
@inlineCallbacks
def edit_config(self, config, target='running', default_operation='merge',
test_option=None, error_option=None, lock_timeout=-1):
"""
Loads all or part of the specified config to the target configuration datastore with the ability to lock
the datastore during the edit. To change multiple items, use your own calls to lock/unlock instead of
using the lock_timeout value
:param config is the configuration, which must be rooted in the config element. It can be specified
either as a string or an Element.format="xml"
:param target is the name of the configuration datastore being edited
:param default_operation if specified must be one of { 'merge', 'replace', or 'none' }
:param test_option if specified must be one of { 'test_then_set', 'set' }
:param error_option if specified must be one of { 'stop-on-error', 'continue-on-error', 'rollback-on-error' }
The 'rollback-on-error' error_option depends on the :rollback-on-error capability.
:param lock_timeout if >0, the maximum number of seconds to hold a lock on the datastore while the edit
operation is underway
:return: (defeered) for RpcReply
"""
if lock_timeout > 0:
try:
request = self.lock(target, lock_timeout)
yield request
except Exception as e:
log.exception('edit_config-lock', e=e)
raise
try:
yield asleep(random.uniform(0.1, 2.0)) # Simulate NETCONF request delay
except Exception as e:
log.exception('edit_config', e=e)
raise
finally:
if lock_timeout > 0:
yield self.unlock(target)
# TODO: Customize if needed...
xml = _dummy_xml
returnValue(RPCReply(xml))
@inlineCallbacks
def rpc(self, rpc_string):
"""
Custom RPC request
:param rpc_string: (string) RPC request
:return: (defeered) for GetReply
"""
yield asleep(random.uniform(0.1, 2.0)) # Simulate NETCONF request delay
# TODO: Customize if needed...
xml = _dummy_xml
returnValue(RPCReply(xml))