Repository: zhmcclient/python-zhmcclient

Issue:
Performance issue with "find()" when filtering on "object-id"
Is "find" method is iterates over items?

For example
cpc.adapters.find(**{'object-id': 'xxxxxxxx'})

If it iterates over items then we should have DPM API to filter item for better performance.


Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/12173391/badge)](https://coveralls.io/builds/12173391)

Coverage decreased (-0.2%) to 87.796% when pulling **f478d0563ac7476cfe45449ec6fc6a7d257922b8 on andy/improve-objectid-filter** into **7a96fc49c5f66a7f4c6a2236c4a6ef5d7ee412ca on master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/12178874/badge)](https://coveralls.io/builds/12178874)

Coverage decreased (-0.2%) to 87.796% when pulling **83108127c8a840c250ee69b785bf174734e8d584 on andy/improve-objectid-filter** into **7a96fc49c5f66a7f4c6a2236c4a6ef5d7ee412ca on master**.

andy-maier: +1

Patch:
diff --git a/zhmcclient/_activation_profile.py b/zhmcclient/_activation_profile.py
index bc0e538..0734caa 100644
--- a/zhmcclient/_activation_profile.py
+++ b/zhmcclient/_activation_profile.py
@@ -104,6 +104,8 @@ class ActivationProfileManager(BaseManager):
             resource_class=ActivationProfile,
             session=cpc.manager.session,
             parent=cpc,
+            base_uri='{}/{}-activation-profiles'.format(cpc.uri, profile_type),
+            oid_prop='name',  # This is an exception!
             uri_prop='element-uri',
             name_prop='name',
             query_props=query_props)
@@ -165,27 +167,32 @@ class ActivationProfileManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        query_parms, client_filters = self._divide_filter_args(filter_args)
-
-        resources_name = self._profile_type + '-activation-profiles'
-        uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
-
         resource_obj_list = []
-        result = self.session.get(uri)
-        if result:
-            props_list = result[resources_name]
-            for props in props_list:
-
-                resource_obj = self.resource_class(
-                    manager=self,
-                    uri=props[self._uri_prop],
-                    name=props.get(self._name_prop, None),
-                    properties=props)
-
-                if self._matches_filters(resource_obj, client_filters):
-                    resource_obj_list.append(resource_obj)
-                    if full_properties:
-                        resource_obj.pull_full_properties()
+        resource_obj = self._try_optimized_lookup(filter_args)
+        if resource_obj:
+            resource_obj_list.append(resource_obj)
+            # It already has full properties
+        else:
+            query_parms, client_filters = self._divide_filter_args(filter_args)
+
+            resources_name = self._profile_type + '-activation-profiles'
+            uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
+
+            result = self.session.get(uri)
+            if result:
+                props_list = result[resources_name]
+                for props in props_list:
+
+                    resource_obj = self.resource_class(
+                        manager=self,
+                        uri=props[self._uri_prop],
+                        name=props.get(self._name_prop, None),
+                        properties=props)
+
+                    if self._matches_filters(resource_obj, client_filters):
+                        resource_obj_list.append(resource_obj)
+                        if full_properties:
+                            resource_obj.pull_full_properties()
 
         self._name_uri_cache.update_from(resource_obj_list)
         return resource_obj_list
diff --git a/zhmcclient/_adapter.py b/zhmcclient/_adapter.py
index bff2f35..4f1e8c3 100644
--- a/zhmcclient/_adapter.py
+++ b/zhmcclient/_adapter.py
@@ -112,6 +112,8 @@ class AdapterManager(BaseManager):
             resource_class=Adapter,
             session=cpc.manager.session,
             parent=cpc,
+            base_uri='/api/adapters',
+            oid_prop='object-id',
             uri_prop='object-uri',
             name_prop='name',
             query_props=query_props)
@@ -160,27 +162,32 @@ class AdapterManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        query_parms, client_filters = self._divide_filter_args(filter_args)
-
-        resources_name = 'adapters'
-        uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
-
         resource_obj_list = []
-        result = self.session.get(uri)
-        if result:
-            props_list = result[resources_name]
-            for props in props_list:
-
-                resource_obj = self.resource_class(
-                    manager=self,
-                    uri=props[self._uri_prop],
-                    name=props.get(self._name_prop, None),
-                    properties=props)
-
-                if self._matches_filters(resource_obj, client_filters):
-                    resource_obj_list.append(resource_obj)
-                    if full_properties:
-                        resource_obj.pull_full_properties()
+        resource_obj = self._try_optimized_lookup(filter_args)
+        if resource_obj:
+            resource_obj_list.append(resource_obj)
+            # It already has full properties
+        else:
+            query_parms, client_filters = self._divide_filter_args(filter_args)
+
+            resources_name = 'adapters'
+            uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
+
+            result = self.session.get(uri)
+            if result:
+                props_list = result[resources_name]
+                for props in props_list:
+
+                    resource_obj = self.resource_class(
+                        manager=self,
+                        uri=props[self._uri_prop],
+                        name=props.get(self._name_prop, None),
+                        properties=props)
+
+                    if self._matches_filters(resource_obj, client_filters):
+                        resource_obj_list.append(resource_obj)
+                        if full_properties:
+                            resource_obj.pull_full_properties()
 
         self._name_uri_cache.update_from(resource_obj_list)
         return resource_obj_list
@@ -242,6 +249,22 @@ class Adapter(BaseResource):
     (in this case, :class:`~zhmcclient.AdapterManager`).
     """
 
+    # Name of property for port URIs, dependent on adapter family
+    port_uris_prop_by_family = {
+        'ficon': 'storage-port-uris',
+        'osa': 'network-port-uris',
+        'roce': 'network-port-uris',
+        'hipersockets': 'network-port-uris',
+    }
+
+    # URI segment for port URIs, dependent on adapter family
+    port_uri_segment_by_family = {
+        'ficon': 'storage-ports',
+        'osa': 'network-ports',
+        'roce': 'network-ports',
+        'hipersockets': 'network-ports',
+    }
+
     def __init__(self, manager, uri, name=None, properties=None):
         # This function should not go into the docs.
         #   manager (:class:`~zhmcclient.AdapterManager`):
@@ -259,6 +282,8 @@ class Adapter(BaseResource):
         super(Adapter, self).__init__(manager, uri, name, properties)
         # The manager objects for child resources (with lazy initialization):
         self._ports = None
+        self._port_uris_prop = None
+        self._port_uri_segment = None
 
     @property
     def ports(self):
@@ -271,6 +296,39 @@ class Adapter(BaseResource):
             self._ports = PortManager(self)
         return self._ports
 
+    @property
+    def port_uris_prop(self):
+        """
+        :term:`string`: Name of adapter property that specifies the adapter
+        port URIs, or the empty string ('') for adapters without ports.
+
+        For example, 'network-port-uris' for a network adapter.
+        """
+        if self._port_uris_prop is None:
+            family = self.get_property('adapter-family')
+            try:
+                self._port_uris_prop = self.port_uris_prop_by_family[family]
+            except KeyError:
+                self._port_uris_prop = ''
+        return self._port_uris_prop
+
+    @property
+    def port_uri_segment(self):
+        """
+        :term:`string`: Adapter type specific URI segment for adapter port
+        URIs, or the empty string ('') for adapters without ports.
+
+        For example, 'network-ports' for a network adapter.
+        """
+        if self._port_uri_segment is None:
+            family = self.get_property('adapter-family')
+            try:
+                self._port_uri_segment = self.port_uri_segment_by_family[
+                    family]
+            except KeyError:
+                self._port_uri_segment = ''
+        return self._port_uri_segment
+
     @logged_api_call
     def delete(self):
         """
diff --git a/zhmcclient/_cpc.py b/zhmcclient/_cpc.py
index 1713c7e..2428622 100644
--- a/zhmcclient/_cpc.py
+++ b/zhmcclient/_cpc.py
@@ -89,6 +89,8 @@ class CpcManager(BaseManager):
             resource_class=Cpc,
             session=client.session,
             parent=None,
+            base_uri='/api/cpcs',
+            oid_prop='object-id',
             uri_prop='object-uri',
             name_prop='name',
             query_props=query_props)
@@ -128,27 +130,32 @@ class CpcManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        query_parms, client_filters = self._divide_filter_args(filter_args)
-
-        resources_name = 'cpcs'
-        uri = '/api/{}{}'.format(resources_name, query_parms)
-
         resource_obj_list = []
-        result = self.session.get(uri)
-        if result:
-            props_list = result[resources_name]
-            for props in props_list:
-
-                resource_obj = self.resource_class(
-                    manager=self,
-                    uri=props[self._uri_prop],
-                    name=props.get(self._name_prop, None),
-                    properties=props)
-
-                if self._matches_filters(resource_obj, client_filters):
-                    resource_obj_list.append(resource_obj)
-                    if full_properties:
-                        resource_obj.pull_full_properties()
+        resource_obj = self._try_optimized_lookup(filter_args)
+        if resource_obj:
+            resource_obj_list.append(resource_obj)
+            # It already has full properties
+        else:
+            query_parms, client_filters = self._divide_filter_args(filter_args)
+
+            resources_name = 'cpcs'
+            uri = '/api/{}{}'.format(resources_name, query_parms)
+
+            result = self.session.get(uri)
+            if result:
+                props_list = result[resources_name]
+                for props in props_list:
+
+                    resource_obj = self.resource_class(
+                        manager=self,
+                        uri=props[self._uri_prop],
+                        name=props.get(self._name_prop, None),
+                        properties=props)
+
+                    if self._matches_filters(resource_obj, client_filters):
+                        resource_obj_list.append(resource_obj)
+                        if full_properties:
+                            resource_obj.pull_full_properties()
 
         self._name_uri_cache.update_from(resource_obj_list)
         return resource_obj_list
diff --git a/zhmcclient/_hba.py b/zhmcclient/_hba.py
index e2b7f1f..0bf0d63 100644
--- a/zhmcclient/_hba.py
+++ b/zhmcclient/_hba.py
@@ -61,6 +61,8 @@ class HbaManager(BaseManager):
             resource_class=Hba,
             session=partition.manager.session,
             parent=partition,
+            base_uri='{}/hbas'.format(partition.uri),
+            oid_prop='element-id',
             uri_prop='element-uri',
             name_prop='name',
             query_props=[],
diff --git a/zhmcclient/_lpar.py b/zhmcclient/_lpar.py
index 80df8f5..d794d39 100644
--- a/zhmcclient/_lpar.py
+++ b/zhmcclient/_lpar.py
@@ -72,6 +72,8 @@ class LparManager(BaseManager):
             resource_class=Lpar,
             session=cpc.manager.session,
             parent=cpc,
+            base_uri='/api/logical-partitions',
+            oid_prop='object-id',
             uri_prop='object-uri',
             name_prop='name',
             query_props=query_props)
@@ -120,27 +122,32 @@ class LparManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        query_parms, client_filters = self._divide_filter_args(filter_args)
-
-        resources_name = 'logical-partitions'
-        uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
-
         resource_obj_list = []
-        result = self.session.get(uri)
-        if result:
-            props_list = result[resources_name]
-            for props in props_list:
-
-                resource_obj = self.resource_class(
-                    manager=self,
-                    uri=props[self._uri_prop],
-                    name=props.get(self._name_prop, None),
-                    properties=props)
-
-                if self._matches_filters(resource_obj, client_filters):
-                    resource_obj_list.append(resource_obj)
-                    if full_properties:
-                        resource_obj.pull_full_properties()
+        resource_obj = self._try_optimized_lookup(filter_args)
+        if resource_obj:
+            resource_obj_list.append(resource_obj)
+            # It already has full properties
+        else:
+            query_parms, client_filters = self._divide_filter_args(filter_args)
+
+            resources_name = 'logical-partitions'
+            uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
+
+            result = self.session.get(uri)
+            if result:
+                props_list = result[resources_name]
+                for props in props_list:
+
+                    resource_obj = self.resource_class(
+                        manager=self,
+                        uri=props[self._uri_prop],
+                        name=props.get(self._name_prop, None),
+                        properties=props)
+
+                    if self._matches_filters(resource_obj, client_filters):
+                        resource_obj_list.append(resource_obj)
+                        if full_properties:
+                            resource_obj.pull_full_properties()
 
         self._name_uri_cache.update_from(resource_obj_list)
         return resource_obj_list
diff --git a/zhmcclient/_manager.py b/zhmcclient/_manager.py
index 5b6ccf2..8a11ac5 100644
--- a/zhmcclient/_manager.py
+++ b/zhmcclient/_manager.py
@@ -34,7 +34,7 @@ import warnings
 from requests.utils import quote
 
 from ._logging import get_logger, logged_api_call
-from ._exceptions import NotFound, NoUniqueMatch
+from ._exceptions import NotFound, NoUniqueMatch, HTTPError
 from ._utils import repr_list
 
 __all__ = ['BaseManager']
@@ -182,8 +182,9 @@ class BaseManager(object):
     documented and may change incompatibly.
     """
 
-    def __init__(self, resource_class, session, parent, uri_prop, name_prop,
-                 query_props, list_has_name=True):
+    def __init__(self, resource_class, session, parent, base_uri,
+                 oid_prop, uri_prop, name_prop, query_props,
+                 list_has_name=True):
         # This method intentionally has no docstring, because it is internal.
         #
         # Parameters:
@@ -197,6 +198,16 @@ class BaseManager(object):
         #     Parent resource defining the scope for this manager.
         #     `None`, if the manager has no parent, i.e. when it manages
         #     top-level resources (e.g. CPC).
+        #   base_uri (string):
+        #     Base URI of the resources of this manager. The base URI has no
+        #     trailing slash and becomes the resource URI by appending '/' and
+        #     the value of the property specified in 'oid_prop'.
+        #     Must not be `None`.
+        #   oid_prop (string):
+        #     Name of the resource property whose value is appended to the
+        #     base URI to form the resource URI (e.g. 'object-id' or
+        #     'element-id').
+        #     Must not be `None`.
         #   uri_prop (string):
         #     Name of the resource property that is the canonical URI path of
         #     the resource (e.g. 'object-uri' or 'element-uri').
@@ -222,6 +233,8 @@ class BaseManager(object):
         # so we test those that are not surfaced through the init code:
         assert resource_class is not None
         assert session is not None
+        assert base_uri is not None
+        assert oid_prop is not None
         assert uri_prop is not None
         assert name_prop is not None
         assert query_props is not None
@@ -229,6 +242,8 @@ class BaseManager(object):
         self._resource_class = resource_class
         self._session = session
         self._parent = parent
+        self._base_uri = base_uri
+        self._oid_prop = oid_prop
         self._uri_prop = uri_prop
         self._name_prop = name_prop
         self._query_props = query_props
@@ -247,6 +262,8 @@ class BaseManager(object):
             "  _resource_class = {_resource_class!r}\n"
             "  _session = {_session_classname} at 0x{_session_id:08x}\n"
             "  _parent = {_parent_classname} at 0x{_parent_id:08x}\n"
+            "  _base_uri = {_base_uri!r}\n"
+            "  _oid_prop = {_oid_prop!r}\n"
             "  _uri_prop = {_uri_prop!r}\n"
             "  _name_prop = {_name_prop!r}\n"
             "  _query_props = {_query_props}\n"
@@ -260,6 +277,8 @@ class BaseManager(object):
                 _session_id=id(self._session),
                 _parent_classname=self._parent.__class__.__name__,
                 _parent_id=id(self._parent),
+                _base_uri=self._base_uri,
+                _oid_prop=self._oid_prop,
                 _uri_prop=self._uri_prop,
                 _name_prop=self._name_prop,
                 _query_props=repr_list(self._query_props, indent=2),
@@ -300,6 +319,59 @@ class BaseManager(object):
         """
         self._name_uri_cache.invalidate()
 
+    def _try_optimized_lookup(self, filter_args):
+        """
+        Try to optimize the lookup by checking whether the filter arguments
+        specify the property that is used as the last segment in the resource
+        URI, with a plain string as match value (i.e. not using regular
+        expression matching).
+
+        If so, the lookup is performed by constructing the resource URI from
+        the specified filter argument, and by issuing a Get Properties
+        operation on that URI, returning a single resource object.
+
+        Parameters:
+
+          filter_args (dict):
+            Filter arguments. For details, see :ref:`Filtering`.
+
+        Returns:
+
+          resource object, or `None` if the optimization was not possible.
+        """
+        if filter_args is not None and \
+                len(filter_args) == 1 and \
+                self._oid_prop in filter_args:
+
+            oid_match = filter_args[self._oid_prop]
+            if re.match(r'^[a-zA-Z0-9_\-]+$', oid_match):
+                # The match string is a plain string (not a reg.expression)
+
+                # Construct the resource URI from the filter property
+                # and issue a Get <Resource> Properties on that URI
+
+                uri = self._base_uri + '/' + oid_match
+
+                try:
+                    props = self.session.get(uri)
+                except HTTPError as exc:
+                    if exc.http_status == 404 and exc.reason == 1:
+                        # No such resource
+                        return None
+                    raise
+
+                resource_obj = self.resource_class(
+                    manager=self,
+                    uri=props[self._uri_prop],
+                    name=props.get(self._name_prop, None),
+                    properties=props)
+
+                resource_obj._full_properties = True
+
+                return resource_obj
+
+        return None
+
     def _divide_filter_args(self, filter_args):
         """
         Divide the filter arguments into filter query parameters for filtering
diff --git a/zhmcclient/_nic.py b/zhmcclient/_nic.py
index 63a7c39..480b11d 100644
--- a/zhmcclient/_nic.py
+++ b/zhmcclient/_nic.py
@@ -62,6 +62,8 @@ class NicManager(BaseManager):
             resource_class=Nic,
             session=partition.manager.session,
             parent=partition,
+            base_uri='{}/nics'.format(partition.uri),
+            oid_prop='element-id',
             uri_prop='element-uri',
             name_prop='name',
             query_props=[],
diff --git a/zhmcclient/_partition.py b/zhmcclient/_partition.py
index f29b1b9..a66ae40 100644
--- a/zhmcclient/_partition.py
+++ b/zhmcclient/_partition.py
@@ -81,6 +81,8 @@ class PartitionManager(BaseManager):
             resource_class=Partition,
             session=cpc.manager.session,
             parent=cpc,
+            base_uri='/api/partitions',
+            oid_prop='object-id',
             uri_prop='object-uri',
             name_prop='name',
             query_props=query_props)
@@ -130,27 +132,34 @@ class PartitionManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        query_parms, client_filters = self._divide_filter_args(filter_args)
-
-        resources_name = 'partitions'
-        uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
 
         resource_obj_list = []
-        result = self.session.get(uri)
-        if result:
-            props_list = result[resources_name]
-            for props in props_list:
-
-                resource_obj = self.resource_class(
-                    manager=self,
-                    uri=props[self._uri_prop],
-                    name=props.get(self._name_prop, None),
-                    properties=props)
-
-                if self._matches_filters(resource_obj, client_filters):
-                    resource_obj_list.append(resource_obj)
-                    if full_properties:
-                        resource_obj.pull_full_properties()
+
+        resource_obj = self._try_optimized_lookup(filter_args)
+        if resource_obj:
+            resource_obj_list.append(resource_obj)
+            # It already has full properties
+        else:
+            query_parms, client_filters = self._divide_filter_args(filter_args)
+
+            resources_name = 'partitions'
+            uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
+
+            result = self.session.get(uri)
+            if result:
+                props_list = result[resources_name]
+                for props in props_list:
+
+                    resource_obj = self.resource_class(
+                        manager=self,
+                        uri=props[self._uri_prop],
+                        name=props.get(self._name_prop, None),
+                        properties=props)
+
+                    if self._matches_filters(resource_obj, client_filters):
+                        resource_obj_list.append(resource_obj)
+                        if full_properties:
+                            resource_obj.pull_full_properties()
 
         self._name_uri_cache.update_from(resource_obj_list)
         return resource_obj_list
diff --git a/zhmcclient/_port.py b/zhmcclient/_port.py
index f670077..1cfccc0 100644
--- a/zhmcclient/_port.py
+++ b/zhmcclient/_port.py
@@ -56,6 +56,11 @@ class PortManager(BaseManager):
             resource_class=Port,
             session=adapter.manager.session,
             parent=adapter,
+            base_uri='',
+            # TODO: Re-enable the following when unit/test_hba.py has been
+            # converted to using the zhmcclient mock support:
+            # base_uri='{}/{}'.format(adapter.uri, adapter.port_uri_segment),
+            oid_prop='element-id',
             uri_prop='element-uri',
             name_prop='name',
             query_props=[],
@@ -74,6 +79,8 @@ class PortManager(BaseManager):
         """
         List the Ports of this Adapter.
 
+        If the adapter does not have any ports, an empty list is returned.
+
         Authorization requirements:
 
         * Object-access permission to this Adapter.
@@ -104,35 +111,32 @@ class PortManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
+        uris_prop = self.adapter.port_uris_prop
+        if not uris_prop:
+            # Adapter does not have any ports
+            return []
+
+        uris = self.adapter.get_property(uris_prop)
+        assert uris is not None
+
+        # TODO: Remove the following circumvention once fixed.
+        # The following line circumvents a bug for FCP adapters that sometimes
+        # causes duplicate URIs to show up in this property:
+        uris = list(set(uris))
+
         resource_obj_list = []
-        storage_family = ('ficon')
-        network_family = ('osa', 'roce', 'hipersockets')
-        if self.adapter.get_property('adapter-family') in storage_family:
-            uris = self.adapter.get_property('storage-port-uris')
-
-            # TODO: Remove the following circumvention once fixed.
-            # The following line circumvents a bug that sometimes causes
-            # duplicate URIs to show up in this property, by reducing the
-            # array to the unique URIs:
-            uris = list(set(uris))
-
-        elif self.adapter.get_property('adapter-family') in network_family:
-            uris = self.adapter.get_property('network-port-uris')
-        else:
-            return resource_obj_list
-        if uris:
-            for uri in uris:
-
-                resource_obj = self.resource_class(
-                    manager=self,
-                    uri=uri,
-                    name=None,
-                    properties=None)
-
-                if self._matches_filters(resource_obj, filter_args):
-                    resource_obj_list.append(resource_obj)
-                    if full_properties:
-                        resource_obj.pull_full_properties()
+        for uri in uris:
+
+            resource_obj = self.resource_class(
+                manager=self,
+                uri=uri,
+                name=None,
+                properties=None)
+
+            if self._matches_filters(resource_obj, filter_args):
+                resource_obj_list.append(resource_obj)
+                if full_properties:
+                    resource_obj.pull_full_properties()
 
         self._name_uri_cache.update_from(resource_obj_list)
         return resource_obj_list
diff --git a/zhmcclient/_virtual_function.py b/zhmcclient/_virtual_function.py
index 81beecd..a4738d5 100644
--- a/zhmcclient/_virtual_function.py
+++ b/zhmcclient/_virtual_function.py
@@ -60,6 +60,8 @@ class VirtualFunctionManager(BaseManager):
             resource_class=VirtualFunction,
             session=partition.manager.session,
             parent=partition,
+            base_uri='{}/virtual-functions'.format(partition.uri),
+            oid_prop='element-id',
             uri_prop='element-uri',
             name_prop='name',
             query_props=[],
diff --git a/zhmcclient/_virtual_switch.py b/zhmcclient/_virtual_switch.py
index df8695a..7efbd74 100644
--- a/zhmcclient/_virtual_switch.py
+++ b/zhmcclient/_virtual_switch.py
@@ -72,6 +72,8 @@ class VirtualSwitchManager(BaseManager):
             resource_class=VirtualSwitch,
             session=cpc.manager.session,
             parent=cpc,
+            base_uri='/api/virtual-switches',
+            oid_prop='object-id',
             uri_prop='object-uri',
             name_prop='name',
             query_props=query_props)
@@ -121,27 +123,32 @@ class VirtualSwitchManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        query_parms, client_filters = self._divide_filter_args(filter_args)
-
-        resources_name = 'virtual-switches'
-        uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
-
         resource_obj_list = []
-        result = self.session.get(uri)
-        if result:
-            props_list = result[resources_name]
-            for props in props_list:
-
-                resource_obj = self.resource_class(
-                    manager=self,
-                    uri=props[self._uri_prop],
-                    name=props.get(self._name_prop, None),
-                    properties=props)
-
-                if self._matches_filters(resource_obj, client_filters):
-                    resource_obj_list.append(resource_obj)
-                    if full_properties:
-                        resource_obj.pull_full_properties()
+        resource_obj = self._try_optimized_lookup(filter_args)
+        if resource_obj:
+            resource_obj_list.append(resource_obj)
+            # It already has full properties
+        else:
+            query_parms, client_filters = self._divide_filter_args(filter_args)
+
+            resources_name = 'virtual-switches'
+            uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms)
+
+            result = self.session.get(uri)
+            if result:
+                props_list = result[resources_name]
+                for props in props_list:
+
+                    resource_obj = self.resource_class(
+                        manager=self,
+                        uri=props[self._uri_prop],
+                        name=props.get(self._name_prop, None),
+                        properties=props)
+
+                    if self._matches_filters(resource_obj, client_filters):
+                        resource_obj_list.append(resource_obj)
+                        if full_properties:
+                            resource_obj.pull_full_properties()
 
         self._name_uri_cache.update_from(resource_obj_list)
         return resource_obj_list


Test patch:
diff --git a/tests/unit/test_adapter.py b/tests/unit/test_adapter.py
index 98c1518..e126499 100755
--- a/tests/unit/test_adapter.py
+++ b/tests/unit/test_adapter.py
@@ -276,7 +276,7 @@ class AdapterTests(unittest.TestCase):
         self.add_standard_osa()
         self.add_standard_hipersocket()
         filter_args = {
-            'name': self.osa1_name + 'foo',
+            'name': self.osa1_name + '_notfound',
         }
 
         adapters = self.cpc.adapters.list(filter_args=filter_args)
@@ -304,7 +304,7 @@ class AdapterTests(unittest.TestCase):
         self.add_standard_osa()
         self.add_standard_hipersocket()
         filter_args = {
-            'object-id': self.osa1_id + 'foo',
+            'object-id': self.osa1_id + '_notfound',
         }
 
         adapters = self.cpc.adapters.list(filter_args=filter_args)
@@ -336,7 +336,7 @@ class AdapterTests(unittest.TestCase):
         self.add_standard_hipersocket()
         filter_args = {
             'name': self.osa1_name,
-            'object-id': self.osa1_id + 'foo',
+            'object-id': self.osa1_id + '_notfound',
         }
 
         adapters = self.cpc.adapters.list(filter_args=filter_args)
@@ -350,7 +350,7 @@ class AdapterTests(unittest.TestCase):
         self.add_standard_osa()
         self.add_standard_hipersocket()
         filter_args = {
-            'name': self.osa1_name + 'foo',
+            'name': self.osa1_name + '_notfound',
             'object-id': self.osa1_id,
         }
 
@@ -365,8 +365,8 @@ class AdapterTests(unittest.TestCase):
         self.add_standard_osa()
         self.add_standard_hipersocket()
         filter_args = {
-            'name': self.osa1_name + 'foo',
-            'object-id': self.osa1_id + 'foo',
+            'name': self.osa1_name + '_notfound',
+            'object-id': self.osa1_id + '_notfound',
         }
 
         adapters = self.cpc.adapters.list(filter_args=filter_args)
@@ -379,11 +379,9 @@ class AdapterTests(unittest.TestCase):
         self.add_standard_osa()
         self.add_standard_hipersocket()
         filter_args = {
-            'name': [self.osa1_name, self.hs2_name + 'foo'],
+            'name': [self.osa1_name, self.hs2_name + '_notfound'],
         }
 
-        # import pdb; pdb.set_trace()
-
         adapters = self.cpc.adapters.list(filter_args=filter_args)
 
         self.assertEqual(len(adapters), 1)
@@ -396,7 +394,7 @@ class AdapterTests(unittest.TestCase):
         self.add_standard_osa()
         self.add_standard_hipersocket()
         filter_args = {
-            'name': [self.hs2_name + 'foo', self.osa1_name],
+            'name': [self.hs2_name + '_notfound', self.osa1_name],
         }
 
         adapters = self.cpc.adapters.list(filter_args=filter_args)
diff --git a/tests/unit/test_manager.py b/tests/unit/test_manager.py
index 2e23d15..54085aa 100755
--- a/tests/unit/test_manager.py
+++ b/tests/unit/test_manager.py
@@ -55,6 +55,8 @@ class MyManager(BaseManager):
             resource_class=MyResource,
             session=session,
             parent=None,  # a top-level resource
+            base_uri='/api/myresources/',
+            oid_prop='fake_object_id',
             uri_prop='fake_uri_prop',
             name_prop='fake_name_prop',
             query_props=[])
@@ -165,6 +167,8 @@ class Manager1Tests(unittest.TestCase):
             resource_class=MyResource,
             session=self.session,
             parent=None,  # a top-level resource
+            base_uri='/api/myresources/',
+            oid_prop='fake_object_id',
             uri_prop='fake_uri_prop',
             name_prop='fake_name_prop',
             query_props=[])
diff --git a/tests/unit/test_port.py b/tests/unit/test_port.py
index 7219c21..ec0cbec 100755
--- a/tests/unit/test_port.py
+++ b/tests/unit/test_port.py
@@ -143,9 +143,7 @@ class PortTests(unittest.TestCase):
 
             self.assertEqual(len(ports), len(uris))
             for idx, port in enumerate(ports):
-                self.assertEqual(
-                    port.properties['element-uri'],
-                    uris[idx])
+                self.assertTrue(port.properties['element-uri'] in uris)
                 self.assertFalse(port.full_properties)
                 self.assertEqual(port.manager, port_mgr)
 
diff --git a/tests/unit/test_resource.py b/tests/unit/test_resource.py
index 6cc62cc..110c4d3 100755
--- a/tests/unit/test_resource.py
+++ b/tests/unit/test_resource.py
@@ -52,6 +52,8 @@ class MyManager(BaseManager):
             resource_class=MyResource,
             session=session,
             parent=None,  # a top-level resource
+            base_uri='/api/myresources/',
+            oid_prop='fake_object_id',
             uri_prop='fake-uri-prop',
             name_prop='fake-name-prop',
             query_props=['qp1', 'qp2'])