Skip to content

Commit

Permalink
Fixed proxy issues for RPC timeout mechanic
Browse files Browse the repository at this point in the history
  • Loading branch information
Galoshi committed Mar 27, 2024
1 parent 3985d90 commit 4d73fe6
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 41 deletions.
2 changes: 1 addition & 1 deletion source/rpc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, address="192.168.0.69", api_path="/api/rpc/v1/", timeout=SOCK
self.timeout = timeout
self.baseURL = "http://" + self.address + self.api_path
self.mainURL = self.baseURL + "com.ifm.efector/"
self.mainProxy = MainProxy(address=self.address, url=self.mainURL, timeout=self.timeout, device=self)
self.mainProxy = MainProxy(url=self.mainURL, timeout=self.timeout, device=self)
self.tcpIpPort = int(self.getParameter("PcicTcpPort"))
self.deviceMeta = self._getDeviceMeta()
self._session = None
Expand Down
83 changes: 44 additions & 39 deletions source/rpc/proxy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import socket
import xmlrpc.client
from contextlib import contextmanager
from threading import Timer
Expand All @@ -9,28 +8,28 @@
class BaseProxy(object):
"""Base class for all proxies."""

def __init__(self, address, url, timeout=SOCKET_TIMEOUT):
def __init__(self, url, device, timeout=SOCKET_TIMEOUT):
"""Initialize the actual xmlrpc.client.ServerProxy from given url.
Args:
address (str): ip address of host
url (str): url for xmlrpc.client.ServerProxy
timeout (float): argument can be a non-negative floating point number
expressing seconds, or None. If None, SOCKET_TIMEOUT value is used as default
device (obj): device
timeout (float): Timeout values which is valid for the BaseProxy.
Argument can be a non-negative floating point number expressing seconds, or None.
If None, SOCKET_TIMEOUT value is used as default
"""
try:
self.__transport = xmlrpc.client.Transport()
self.__transport.make_connection(host=address)
self.__proxy = xmlrpc.client.ServerProxy(uri=url, allow_none=True)
self.__transport = self.__proxy("transport")
self.__transport.make_connection(host=device.address)
getattr(self.__transport, "_connection")[1].timeout = timeout
self.__proxy = xmlrpc.client.ServerProxy(uri=url, transport=self.__transport)
except TimeoutError:
self.close()

@property
def timeout(self):
if getattr(self.__transport, "_connection")[1]:
return getattr(self.__transport, "_connection")[1].timeout
return socket.getdefaulttimeout

@timeout.setter
def timeout(self, value):
Expand All @@ -54,38 +53,43 @@ def __getattr__(self, name):
def close(self):
self.__transport.close()
self.__transport = None
self.__connection = None
self.__proxy = None


class MainProxy(BaseProxy):
"""Proxy representing mainProxy."""

def __init__(self, address, url, timeout, device):
def __init__(self, url, device, timeout):
"""Initialize main proxy member, device and baseURL.
Args:
url (str): url for BaseProxy
device (obj): device
timeout (float): Timeout values which is valid for the MainProxy.
Argument can be a non-negative floating point number expressing seconds, or None.
If None, SOCKET_TIMEOUT value is used as default
"""
self.address = address
self.baseURL = url
self.device = device

super(MainProxy, self).__init__(address, url, timeout)
super(MainProxy, self).__init__(url, device, timeout)

@contextmanager
def requestSession(self, password='', session_id='0' * 32):
def requestSession(self, password='', session_id='0' * 32, timeout=SOCKET_TIMEOUT):
"""Generator for requestSession to be used in with statement.
Args:
password (str): password for session
session_id (str): session id
timeout (float): Timeout values which is valid for the SessionProxy.
Argument can be a non-negative floating point number expressing seconds, or None.
If None, SOCKET_TIMEOUT value is used as default
"""
try:
self.device._sessionId = self.__getattr__('requestSession')(password, session_id)
self.device._sessionURL = self.baseURL + 'session_' + session_id + '/'
self.device._sessionProxy = SessionProxy(url=self.device._sessionURL, device=self.device)
self.device._sessionProxy = SessionProxy(url=self.device._sessionURL,
device=self.device, timeout=timeout)
yield
finally:
try:
Expand All @@ -103,13 +107,13 @@ def requestSession(self, password='', session_id='0' * 32):
class SessionProxy(BaseProxy):
"""Proxy representing sessionProxy."""

def __init__(self, url, device, autoHeartbeat=True, autoHeartbeatInterval=30):
def __init__(self, url, device, timeout=SOCKET_TIMEOUT, autoHeartbeat=True, autoHeartbeatInterval=30):
self.baseURL = url
self.device = device
self.autoHeartbeat = autoHeartbeat
self.autoHeartbeatInterval = autoHeartbeatInterval

super().__init__(url)
super().__init__(url, device, timeout)

if self.autoHeartbeat:
self.heartbeat(self.autoHeartbeatInterval)
Expand All @@ -118,8 +122,6 @@ def __init__(self, url, device, autoHeartbeat=True, autoHeartbeatInterval=30):
else:
self.heartbeat(300)

# self.device._session = Session(sessionProxy=self.proxy, device=self.device)

def heartbeat(self, heartbeatInterval: int) -> int:
"""
Extend the live time of edit-session If the given value is outside the range of "SessionTimeout",
Expand All @@ -145,17 +147,19 @@ def doAutoHeartbeat(self) -> None:
self.autoHeartbeatTimer.start()

@contextmanager
def setOperatingMode(self, mode):
def setOperatingMode(self, mode, timeout=SOCKET_TIMEOUT):
"""Generator for setOperatingMode to be used in with statement.
Args:
mode (int): operating mode
timeout (float): Timeout values which is valid for the EditProxy.
Argument can be a non-negative floating point number expressing seconds, or None.
If None, SOCKET_TIMEOUT value is used as default
"""
try:
self.__getattr__('setOperatingMode')(mode)
self.device._editURL = self.baseURL + 'edit/'
self.device._editProxy = EditProxy(url=self.device._editURL,
device=self.device)
self.device._editProxy = EditProxy(url=self.device._editURL, device=self.device, timeout=timeout)
yield
finally:
self.__getattr__('setOperatingMode')(0)
Expand All @@ -167,26 +171,27 @@ def setOperatingMode(self, mode):
class EditProxy(BaseProxy):
"""Proxy representing editProxy."""

def __init__(self, url, device):
def __init__(self, url, device, timeout=SOCKET_TIMEOUT):
self.baseURL = url
self.device = device

super().__init__(url)

# self.device._edit = Edit(editProxy=self.proxy, device=self.device)
super().__init__(url, device, timeout)

@contextmanager
def editApplication(self, app_index):
def editApplication(self, app_index, timeout=SOCKET_TIMEOUT):
"""Generator for editApplication to be used in with statement.
Args:
app_index (int): application index
timeout (float): Timeout values which is valid for the ApplicationProxy.
Argument can be a non-negative floating point number expressing seconds, or None.
If None, SOCKET_TIMEOUT value is used as default
"""
try:
self.__getattr__('editApplication')(app_index)
self.device._applicationURL = self.baseURL + "application/"
self.device._applicationProxy = ApplicationProxy(url=self.device._applicationURL,
device=self.device)
self.device._applicationProxy = ApplicationProxy(url=self.device._applicationURL, device=self.device,
timeout=timeout)
yield
finally:
self.__getattr__('stopEditingApplication')()
Expand All @@ -198,20 +203,21 @@ def editApplication(self, app_index):
class ApplicationProxy(BaseProxy):
"""Proxy representing editProxy."""

def __init__(self, url, device):
def __init__(self, url, device, timeout=SOCKET_TIMEOUT):
self.baseURL = url
self.device = device

super().__init__(url)

# self.device._application = Application(applicationProxy=self.proxy, device=self.device)
super().__init__(url, device, timeout)

@contextmanager
def editImager(self, imager_index):
def editImager(self, imager_index, timeout=SOCKET_TIMEOUT):
"""Generator for editImager to be used in with statement.
Args:
imager_index (int): imager index
timeout (float): Timeout values which is valid for the ImagerProxy.
Argument can be a non-negative floating point number expressing seconds, or None.
If None, SOCKET_TIMEOUT value is used as default
"""
try:
imager_IDs = [int(x["Id"]) for x in self.proxy.getImagerConfigList()]
Expand All @@ -220,7 +226,8 @@ def editImager(self, imager_index):
"ImagerConfigList or create a new one with method createImagerConfig():\n{}"
.format(imager_index, self.proxy.getImagerConfigList()))
self.device._imagerURL = self.baseURL + 'imager_{0:03d}/'.format(imager_index)
self.device._imagerProxy = ImagerProxy(url=self.device._imagerURL, device=self.device)
self.device._imagerProxy = ImagerProxy(url=self.device._imagerURL, device=self.device,
timeout=timeout)
yield
finally:
self.device._imagerProxy.close()
Expand All @@ -231,10 +238,8 @@ def editImager(self, imager_index):
class ImagerProxy(BaseProxy):
"""Proxy representing editProxy."""

def __init__(self, url, device):
def __init__(self, url, device, timeout=SOCKET_TIMEOUT):
self.baseURL = url
self.device = device

super().__init__(url)

# self.device._imager = Imager(imagerProxy=self.proxy, device=self.device)
super().__init__(url, device, timeout)
3 changes: 2 additions & 1 deletion tests/test_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ def tearDown(self) -> None:
pass

def test_timeout_with_invalid_ip(self):
TIMEOUT_VALUES = [1, 2, 5]
TIMEOUT_VALUES = range(1, 6)
for timeout_value in TIMEOUT_VALUES:
start_time = time.time()
with self.assertRaises(socket.timeout):
with O2x5xxRPCDevice("192.168.0.5", timeout=timeout_value) as device:
device.rpc.getParameter("ActiveApplication")
self.assertEqual(device.mainProxy.timeout, timeout_value)
end_time = time.time()
duration_secs = end_time - start_time
self.assertLess(duration_secs, timeout_value+1)
Expand Down

0 comments on commit 4d73fe6

Please sign in to comment.