Add Accelerator resource watcher; fix bug of the alarm dictionary 89/9489/2
authorZhang Rong(Jon) <rong.zhang@windriver.com>
Sun, 6 Nov 2022 15:28:49 +0000 (23:28 +0800)
committerZhang Rong(Jon) <rong.zhang@windriver.com>
Mon, 7 Nov 2022 01:25:05 +0000 (09:25 +0800)
Issue-ID: INF-325
Signed-off-by: Zhang Rong(Jon) <rong.zhang@windriver.com>
Change-Id: Ib8a31aec10a323b56216662ad88df4b309c1b7eb

12 files changed:
o2app/entrypoints/resource_watcher.py
o2app/service/handlers.py
o2ims/adapter/alarm_loader.py
o2ims/adapter/clients/ocloud_client.py
o2ims/domain/commands.py
o2ims/domain/ocloud.py
o2ims/domain/resource_type.py
o2ims/service/auditor/pserver_acc_handler.py [new file with mode: 0644]
o2ims/service/auditor/pserver_dev_handler.py [new file with mode: 0644]
o2ims/service/watcher/pserver_acc_watcher.py [new file with mode: 0644]
o2ims/service/watcher/pserver_dev_watcher.py [new file with mode: 0644]
tests/integration-ocloud/test_clientdriver_stx.py

index 99d5595..909e86c 100644 (file)
@@ -40,12 +40,17 @@ from o2ims.adapter.clients.ocloud_client import StxMemClient
 from o2ims.service.watcher.pserver_if_watcher import PServerIfWatcher
 from o2ims.adapter.clients.ocloud_client import StxIfClient
 
-from o2ims.service.watcher.pserver_port_watcher import PServerIfPortWatcher
-from o2ims.adapter.clients.ocloud_client import StxIfPortClient
+from o2ims.service.watcher.pserver_port_watcher import PServerIfPortWatcher
+from o2ims.adapter.clients.ocloud_client import StxIfPortClient
 
 from o2ims.service.watcher.pserver_eth_watcher import PServerEthWatcher
 from o2ims.adapter.clients.ocloud_client import StxEthClient
 
+# from o2ims.service.watcher.pserver_dev_watcher import PServerDevWatcher
+# from o2ims.adapter.clients.ocloud_client import StxDevClient
+from o2ims.service.watcher.pserver_acc_watcher import PServerAccWatcher
+from o2ims.adapter.clients.ocloud_client import StxAccClient
+
 from o2common.helper import o2logging
 logger = o2logging.get_logger(__name__)
 
@@ -79,10 +84,14 @@ class WatcherService(cotyledon.Service):
                 PServerMemWatcher(StxMemClient(), self.bus))
             child_pserver.addchild(
                 PServerEthWatcher(StxEthClient(), self.bus))
-            child_if = child_pserver.addchild(
+            child_pserver.addchild(
                 PServerIfWatcher(StxIfClient(), self.bus))
             # child_if.addchild(
             #     PServerIfPortWatcher(StxIfPortClient(), self.bus))
+            # child_pserver.addchild(
+            #     PServerDevWatcher(StxDevClient(), self.bus))
+            child_pserver.addchild(
+                PServerAccWatcher(StxAccClient(), self.bus))
 
             self.worker.add_watcher(root)
 
index cc69fc5..ad75098 100644 (file)
@@ -26,7 +26,8 @@ from o2dms.domain import events as o2dms_events
 from o2ims.service.auditor import ocloud_handler, dms_handler, \
     resourcepool_handler, pserver_handler, pserver_cpu_handler, \
     pserver_mem_handler, pserver_port_handler, pserver_if_handler,\
-    pserver_eth_handler, alarm_handler
+    pserver_eth_handler, pserver_acc_handler, alarm_handler, \
+    pserver_dev_handler
 from o2ims.service.command import notify_handler, registration_handler,\
     notify_alarm_handler
 from o2ims.service.event import ocloud_event, resource_event, \
@@ -72,6 +73,8 @@ COMMAND_HANDLERS = {
     commands.UpdatePserverIf: pserver_if_handler.update_pserver_if,
     commands.UpdatePserverIfPort: pserver_port_handler.update_pserver_port,
     commands.UpdatePserverEth: pserver_eth_handler.update_pserver_eth,
+    commands.UpdatePserverDev: pserver_dev_handler.update_pserver_dev,
+    commands.UpdatePserverAcc: pserver_acc_handler.update_pserver_acc,
     o2dms_cmmands.HandleNfDeploymentStateChanged:
     nfdeployment_handler.handle_nfdeployment_statechanged,
     o2dms_cmmands.InstallNfDeployment:
index 823d0d5..1e73e44 100644 (file)
@@ -29,7 +29,8 @@ class AlarmDictionaryConfigFileRepository(AlarmDictionaryRepository):
         self.dictionary[alarm_dict.entityType] = alarm_dict
 
     def _get(self, alarm_entity_type) -> alarm_obj.AlarmDictionary:
-        return self.dictionary[alarm_entity_type]
+        return self.dictionary[alarm_entity_type] \
+            if alarm_entity_type in self.dictionary else None
 
     def _list(self) -> List[alarm_obj.AlarmDictionary]:
         return [alarm_dict for alarm_dict in self.dictionary.items()]
index ddc644e..7307bcc 100644 (file)
@@ -172,7 +172,37 @@ class StxIfPortClient(BaseClient):
         self.driver.setStxClient(self._pool_id)
 
 
-# internal driver which implement client call to Stx Standalone instance
+class StxDevClient(BaseClient):
+    def __init__(self):
+        super().__init__()
+        self.driver = StxClientImp()
+
+    def _get(self, id) -> ocloudModel.StxGenericModel:
+        return self.driver.getDevice(id)
+
+    def _list(self, **filters) -> List[ocloudModel.StxGenericModel]:
+        return self.driver.getDeviceList(**filters)
+
+    def _set_stx_client(self):
+        self.driver.setStxClient(self._pool_id)
+
+
+class StxAccClient(BaseClient):
+    def __init__(self):
+        super().__init__()
+        self.driver = StxClientImp()
+
+    def _get(self, id) -> ocloudModel.StxGenericModel:
+        return self.driver.getAccelerator(id)
+
+    def _list(self, **filters) -> List[ocloudModel.StxGenericModel]:
+        return self.driver.getAcceleratorList(**filters)
+
+    def _set_stx_client(self):
+        self.driver.setStxClient(self._pool_id)
+
+
+# internal driver which implement client call to Stx Standalone and DC instance
 class StxClientImp(object):
     def __init__(self, stx_client=None, dc_client=None):
         super().__init__()
@@ -446,6 +476,41 @@ class StxClientImp(object):
         return ocloudModel.StxGenericModel(
             ResourceTypeEnum.PSERVER_IF_PORT, portinfo)
 
+    def getDeviceList(self, **filters) -> List[ocloudModel.StxGenericModel]:
+        hostid = filters.get('hostid', None)
+        assert (hostid is not None), 'missing hostid to query pci device list'
+        pci_dev_list = self.stxclient.pci_device.list(hostid)
+        return [ocloudModel.StxGenericModel(
+            ResourceTypeEnum.PSERVER_PCI_DEV,
+            self._devconverter(pci_dev))
+            for pci_dev in pci_dev_list if pci_dev]
+
+    def getDevice(self, id) -> ocloudModel.StxGenericModel:
+        pciinfo = self.stxclient.pci_device.get(id)
+        return ocloudModel.StxGenericModel(
+            ResourceTypeEnum.PSERVER_PCI_DEV, self._devconverter(pciinfo))
+
+    def getAcceleratorList(self, **filters) -> \
+            List[ocloudModel.StxGenericModel]:
+        hostid = filters.get('hostid', None)
+        assert (hostid is not None), 'missing hostid to query accelerator list'
+        pci_dev_list = self.stxclient.pci_device.list(hostid)
+        acc_list = []
+        for pci_dev in pci_dev_list:
+            if pci_dev.pvendor_id in ['8086']:
+                if pci_dev.pdevice_id in ['0d5c', '0d5d']:
+                    logger.info('Accelerator vendor ID: {}, device ID: {}'.
+                                format(pci_dev.pvendor_id, pci_dev.pdevice_id))
+                    acc_list.append(ocloudModel.StxGenericModel(
+                        ResourceTypeEnum.PSERVER_ACC,
+                        self._devconverter(pci_dev)))
+        return acc_list
+
+    def getAccelerator(self, id) -> ocloudModel.StxGenericModel:
+        pciinfo = self.stxclient.pci_device.get(id)
+        return ocloudModel.StxGenericModel(
+            ResourceTypeEnum.PSERVER_ACC, self._devconverter(pciinfo))
+
     def _getIsystems(self):
         return self.stxclient.isystem.list()
 
@@ -490,6 +555,11 @@ class StxClientImp(object):
         setattr(ifs, 'created_at', None)
         return ifs
 
+    @ staticmethod
+    def _devconverter(dev):
+        setattr(dev, 'name', dev.host_uuid.split('-', 1)[0] + '-'+dev.name)
+        return dev
+
     @ staticmethod
     def _k8sconverter(cluster):
         setattr(cluster, 'name', cluster.cloud_name +
index 25c9629..5d58e4c 100644 (file)
@@ -104,6 +104,16 @@ class UpdatePserverIfPort(UpdateResource):
     pass
 
 
+@dataclass
+class UpdatePserverDev(UpdateResource):
+    pass
+
+
+@dataclass
+class UpdatePserverAcc(UpdateResource):
+    pass
+
+
 @dataclass
 class UpdateAlarm(UpdateFaultObject):
     pass
index c6f5fd8..bdc07d9 100644 (file)
@@ -105,8 +105,10 @@ class ResourceType(AgRoot, Serializer):
     def serialize(self):
         d = Serializer.serialize(self)
 
-        d["alarmDictionary"] = CONF.alarm_dictionaries.get(
-            d['name']).serialize()
+        if CONF.alarm_dictionaries.get(d['name']) is not None:
+            d["alarmDictionary"] = CONF.alarm_dictionaries.get(
+                d['name']).serialize()
+
         return d
 
 
index 3a2271a..076d6ae 100644 (file)
@@ -12,6 +12,8 @@ class ResourceTypeEnum(Enum):
     PSERVER_IF = 14
     PSERVER_IF_PORT = 15
     PSERVER_ETH = 16
+    PSERVER_PCI_DEV = 17
+    PSERVER_ACC = 18
 
 
 class ResourceKindEnum(Enum):
diff --git a/o2ims/service/auditor/pserver_acc_handler.py b/o2ims/service/auditor/pserver_acc_handler.py
new file mode 100644 (file)
index 0000000..fc289ee
--- /dev/null
@@ -0,0 +1,127 @@
+# Copyright (C) 2021 Wind River Systems, 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.
+
+# pylint: disable=unused-argument
+from __future__ import annotations
+import uuid
+# import json
+
+from o2ims.domain import commands, events
+from o2ims.domain.stx_object import StxGenericModel
+from o2ims.domain.subscription_obj import NotificationEventEnum
+from o2common.service.unit_of_work import AbstractUnitOfWork
+from o2ims.domain.resource_type import MismatchedModel
+from o2ims.domain.ocloud import Resource, ResourceType
+
+from o2common.helper import o2logging
+logger = o2logging.get_logger(__name__)
+
+
+class InvalidResourceType(Exception):
+    pass
+
+
+def update_pserver_acc(
+    cmd: commands.UpdatePserverAcc,
+    uow: AbstractUnitOfWork
+):
+    stxobj = cmd.data
+    with uow:
+        p_resource = uow.resources.get(cmd.parentid)
+        resourcepool = uow.resource_pools.get(p_resource.resourcePoolId)
+
+        res = uow.session.execute(
+            '''
+            SELECT "resourceTypeId", "oCloudId", "name"
+            FROM "resourceType"
+            WHERE "resourceTypeEnum" = :resource_type_enum
+            ''',
+            dict(resource_type_enum=stxobj.type.name)
+        )
+        first = res.first()
+        if first is None:
+            res_type_name = 'pserver_acc'
+            resourcetype_id = str(uuid.uuid3(
+                uuid.NAMESPACE_URL, res_type_name))
+            uow.resource_types.add(ResourceType(
+                resourcetype_id,
+                res_type_name, stxobj.type,
+                resourcepool.oCloudId,
+                description='An Accelerator resource type of Physical Server'))
+        else:
+            resourcetype_id = first['resourceTypeId']
+
+        resource = uow.resources.get(stxobj.id)
+        if not resource:
+            logger.info("add the accelerator  of pserver:" + stxobj.name
+                        + " update_at: " + str(stxobj.updatetime)
+                        + " id: " + str(stxobj.id)
+                        + " hash: " + str(stxobj.hash))
+            localmodel = create_by(stxobj, p_resource, resourcetype_id)
+            uow.resources.add(localmodel)
+
+            logger.info("Add the accelerator of pserver: " + stxobj.id
+                        + ", name: " + stxobj.name)
+        else:
+            localmodel = resource
+            if is_outdated(localmodel, stxobj):
+                logger.info("update accelerator of pserver:" + stxobj.name
+                            + " update_at: " + str(stxobj.updatetime)
+                            + " id: " + str(stxobj.id)
+                            + " hash: " + str(stxobj.hash))
+                update_by(localmodel, stxobj, p_resource)
+                uow.resources.update(localmodel)
+
+            logger.info("Update the accelerator of pserver: " + stxobj.id
+                        + ", name: " + stxobj.name)
+        uow.commit()
+
+
+def is_outdated(resource: Resource, stxobj: StxGenericModel):
+    return True if resource.hash != stxobj.hash else False
+
+
+def create_by(stxobj: StxGenericModel, parent: Resource, resourcetype_id: str)\
+        -> Resource:
+    # content = json.loads(stxobj.content)
+    resourcetype_id = resourcetype_id
+    resourcepool_id = parent.resourcePoolId
+    parent_id = parent.resourceId
+    gAssetId = ''  # TODO: global ID
+    description = "%s : An Accelerator resource of the physical server"\
+        % stxobj.name
+    resource = Resource(stxobj.id, resourcetype_id, resourcepool_id,
+                        stxobj.name, parent_id, gAssetId, stxobj.content,
+                        description)
+    resource.createtime = stxobj.createtime
+    resource.updatetime = stxobj.updatetime
+    resource.hash = stxobj.hash
+
+    return resource
+
+
+def update_by(target: Resource, stxobj: StxGenericModel,
+              parentid: str) -> None:
+    if target.resourceId != stxobj.id:
+        raise MismatchedModel("Mismatched Id")
+    target.createtime = stxobj.createtime
+    target.updatetime = stxobj.updatetime
+    target.hash = stxobj.hash
+    target.version_number = target.version_number + 1
+    target.events.append(events.ResourceChanged(
+        id=stxobj.id,
+        resourcePoolId=target.resourcePoolId,
+        notificationEventType=NotificationEventEnum.MODIFY,
+        updatetime=stxobj.updatetime
+    ))
diff --git a/o2ims/service/auditor/pserver_dev_handler.py b/o2ims/service/auditor/pserver_dev_handler.py
new file mode 100644 (file)
index 0000000..da94d8a
--- /dev/null
@@ -0,0 +1,127 @@
+# Copyright (C) 2021 Wind River Systems, 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.
+
+# pylint: disable=unused-argument
+from __future__ import annotations
+import uuid
+# import json
+
+from o2ims.domain import commands, events
+from o2ims.domain.stx_object import StxGenericModel
+from o2ims.domain.subscription_obj import NotificationEventEnum
+from o2common.service.unit_of_work import AbstractUnitOfWork
+from o2ims.domain.resource_type import MismatchedModel
+from o2ims.domain.ocloud import Resource, ResourceType
+
+from o2common.helper import o2logging
+logger = o2logging.get_logger(__name__)
+
+
+class InvalidResourceType(Exception):
+    pass
+
+
+def update_pserver_dev(
+    cmd: commands.UpdatePserverDev,
+    uow: AbstractUnitOfWork
+):
+    stxobj = cmd.data
+    with uow:
+        p_resource = uow.resources.get(cmd.parentid)
+        resourcepool = uow.resource_pools.get(p_resource.resourcePoolId)
+
+        res = uow.session.execute(
+            '''
+            SELECT "resourceTypeId", "oCloudId", "name"
+            FROM "resourceType"
+            WHERE "resourceTypeEnum" = :resource_type_enum
+            ''',
+            dict(resource_type_enum=stxobj.type.name)
+        )
+        first = res.first()
+        if first is None:
+            res_type_name = 'pserver_dev'
+            resourcetype_id = str(uuid.uuid3(
+                uuid.NAMESPACE_URL, res_type_name))
+            uow.resource_types.add(ResourceType(
+                resourcetype_id,
+                res_type_name, stxobj.type,
+                resourcepool.oCloudId,
+                description='A Device resource type of Physical Server'))
+        else:
+            resourcetype_id = first['resourceTypeId']
+
+        resource = uow.resources.get(stxobj.id)
+        if not resource:
+            logger.info("add the device of pserver:" + stxobj.name
+                        + " update_at: " + str(stxobj.updatetime)
+                        + " id: " + str(stxobj.id)
+                        + " hash: " + str(stxobj.hash))
+            localmodel = create_by(stxobj, p_resource, resourcetype_id)
+            uow.resources.add(localmodel)
+
+            logger.info("Add the device of pserver: " + stxobj.id
+                        + ", name: " + stxobj.name)
+        else:
+            localmodel = resource
+            if is_outdated(localmodel, stxobj):
+                logger.info("update device of pserver:" + stxobj.name
+                            + " update_at: " + str(stxobj.updatetime)
+                            + " id: " + str(stxobj.id)
+                            + " hash: " + str(stxobj.hash))
+                update_by(localmodel, stxobj, p_resource)
+                uow.resources.update(localmodel)
+
+            logger.info("Update the device of pserver: " + stxobj.id
+                        + ", name: " + stxobj.name)
+        uow.commit()
+
+
+def is_outdated(resource: Resource, stxobj: StxGenericModel):
+    return True if resource.hash != stxobj.hash else False
+
+
+def create_by(stxobj: StxGenericModel, parent: Resource, resourcetype_id: str)\
+        -> Resource:
+    # content = json.loads(stxobj.content)
+    resourcetype_id = resourcetype_id
+    resourcepool_id = parent.resourcePoolId
+    parent_id = parent.resourceId
+    gAssetId = ''  # TODO: global ID
+    description = "%s : A device resource of the physical server"\
+        % stxobj.name
+    resource = Resource(stxobj.id, resourcetype_id, resourcepool_id,
+                        stxobj.name, parent_id, gAssetId, stxobj.content,
+                        description)
+    resource.createtime = stxobj.createtime
+    resource.updatetime = stxobj.updatetime
+    resource.hash = stxobj.hash
+
+    return resource
+
+
+def update_by(target: Resource, stxobj: StxGenericModel,
+              parentid: str) -> None:
+    if target.resourceId != stxobj.id:
+        raise MismatchedModel("Mismatched Id")
+    target.createtime = stxobj.createtime
+    target.updatetime = stxobj.updatetime
+    target.hash = stxobj.hash
+    target.version_number = target.version_number + 1
+    target.events.append(events.ResourceChanged(
+        id=stxobj.id,
+        resourcePoolId=target.resourcePoolId,
+        notificationEventType=NotificationEventEnum.MODIFY,
+        updatetime=stxobj.updatetime
+    ))
diff --git a/o2ims/service/watcher/pserver_acc_watcher.py b/o2ims/service/watcher/pserver_acc_watcher.py
new file mode 100644 (file)
index 0000000..eef23ce
--- /dev/null
@@ -0,0 +1,42 @@
+# Copyright (C) 2021 Wind River Systems, 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.
+
+from o2ims.domain.stx_object import StxGenericModel
+from o2common.service.client.base_client import BaseClient
+# from o2common.service.unit_of_work import AbstractUnitOfWork
+from o2ims.service.watcher.resource_watcher import ResourceWatcher
+from o2ims.domain import commands
+from o2common.service.messagebus import MessageBus
+
+from o2common.helper import o2logging
+logger = o2logging.get_logger(__name__)
+
+
+class PServerAccWatcher(ResourceWatcher):
+    def __init__(self, client: BaseClient,
+                 bus: MessageBus) -> None:
+        super().__init__(client, bus)
+
+    def _targetname(self):
+        return "pserver_acc"
+
+    def _probe(self, parent: StxGenericModel, tags):
+        # Set a tag for children resource
+        self._tags.pool = tags.pool
+        self._set_respool_client()
+
+        hostid = parent.id
+        newmodels = self._client.list(hostid=hostid)
+        return [commands.UpdatePserverAcc(data=m, parentid=hostid)
+                for m in newmodels]
diff --git a/o2ims/service/watcher/pserver_dev_watcher.py b/o2ims/service/watcher/pserver_dev_watcher.py
new file mode 100644 (file)
index 0000000..446aa26
--- /dev/null
@@ -0,0 +1,42 @@
+# Copyright (C) 2021 Wind River Systems, 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.
+
+from o2ims.domain.stx_object import StxGenericModel
+from o2common.service.client.base_client import BaseClient
+# from o2common.service.unit_of_work import AbstractUnitOfWork
+from o2ims.service.watcher.resource_watcher import ResourceWatcher
+from o2ims.domain import commands
+from o2common.service.messagebus import MessageBus
+
+from o2common.helper import o2logging
+logger = o2logging.get_logger(__name__)
+
+
+class PServerDevWatcher(ResourceWatcher):
+    def __init__(self, client: BaseClient,
+                 bus: MessageBus) -> None:
+        super().__init__(client, bus)
+
+    def _targetname(self):
+        return "pserver_dev"
+
+    def _probe(self, parent: StxGenericModel, tags):
+        # Set a tag for children resource
+        self._tags.pool = tags.pool
+        self._set_respool_client()
+
+        hostid = parent.id
+        newmodels = self._client.list(hostid=hostid)
+        return [commands.UpdatePserverDev(data=m, parentid=hostid)
+                for m in newmodels]
index 24de6c4..8ca1780 100644 (file)
@@ -162,6 +162,20 @@ def test_get_if_port_list(real_stx_aio_client):
     assert port1.id == port2.id
 
 
+def test_get_device_list(real_stx_aio_client):
+    stxClientImp = StxClientImp(real_stx_aio_client)
+    assert stxClientImp is not None
+    hostlist = stxClientImp.getPserverList()
+    assert len(hostlist) > 0
+
+    devicelist = stxClientImp.getDeviceList(hostid=hostlist[0].id)
+    assert len(devicelist) > 0
+    dev1 = devicelist[0]
+    dev2 = stxClientImp.getDevice(dev1.id)
+    assert dev1 != dev2
+    assert dev1.id == dev2.id
+
+
 def test_get_res_pool_list(real_stx_aio_client, real_stx_dc_client):
     stxClientImp = StxClientImp(real_stx_aio_client, real_stx_dc_client)
     assert stxClientImp is not None