mirror of https://github.com/OpenKMIP/PyKMIP.git
Add offset and maximum item filtering for the Locate operation
This change updates Locate operation support in the PyKMIP server, allowing users to filter objects using the offset and maximum item constraints. The offset constraint tells the server how many matching items should be skipped before results are returned. The maximum items constraint tells the server how many matching items should be returned. Unit tests and integration tests have been added to test and verify the correctness of this feature. Additionally, the Locate demo scripts have also been updated to support offset and maximum item filtering. Simply use the "--offset-items" and "--maximum-items" flags to specify offset and maximum item values for the Locate script to filter on. Fixes #562
This commit is contained in:
parent
4938f82772
commit
4a6a2eccc1
|
@ -32,6 +32,8 @@ if __name__ == '__main__':
|
||||||
opts, args = parser.parse_args(sys.argv[1:])
|
opts, args = parser.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
config = opts.config
|
config = opts.config
|
||||||
|
offset_items = opts.offset_items
|
||||||
|
maximum_items = opts.maximum_items
|
||||||
name = opts.name
|
name = opts.name
|
||||||
initial_dates = opts.initial_dates
|
initial_dates = opts.initial_dates
|
||||||
state = opts.state
|
state = opts.state
|
||||||
|
@ -43,6 +45,13 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
attribute_factory = AttributeFactory()
|
attribute_factory = AttributeFactory()
|
||||||
|
|
||||||
|
if offset_items and (offset_items < 0):
|
||||||
|
logger.error("Invalid offset items value provided.")
|
||||||
|
sys.exit(-1)
|
||||||
|
if maximum_items and (maximum_items < 0):
|
||||||
|
logger.error("Invalid maximum items value provided.")
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
# Build attributes if any are specified
|
# Build attributes if any are specified
|
||||||
attributes = []
|
attributes = []
|
||||||
if name:
|
if name:
|
||||||
|
@ -159,7 +168,11 @@ if __name__ == '__main__':
|
||||||
config_file=opts.config_file
|
config_file=opts.config_file
|
||||||
) as client:
|
) as client:
|
||||||
try:
|
try:
|
||||||
uuids = client.locate(attributes=attributes)
|
uuids = client.locate(
|
||||||
|
attributes=attributes,
|
||||||
|
offset_items=offset_items,
|
||||||
|
maximum_items=maximum_items
|
||||||
|
)
|
||||||
logger.info("Located uuids: {0}".format(uuids))
|
logger.info("Located uuids: {0}".format(uuids))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
|
@ -35,6 +35,8 @@ if __name__ == '__main__':
|
||||||
username = opts.username
|
username = opts.username
|
||||||
password = opts.password
|
password = opts.password
|
||||||
config = opts.config
|
config = opts.config
|
||||||
|
offset_items = opts.offset_items
|
||||||
|
maximum_items = opts.maximum_items
|
||||||
name = opts.name
|
name = opts.name
|
||||||
initial_dates = opts.initial_dates
|
initial_dates = opts.initial_dates
|
||||||
state = opts.state
|
state = opts.state
|
||||||
|
@ -62,6 +64,13 @@ if __name__ == '__main__':
|
||||||
credential_value
|
credential_value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if offset_items and (offset_items < 0):
|
||||||
|
logger.error("Invalid offset items value provided.")
|
||||||
|
sys.exit(-1)
|
||||||
|
if maximum_items and (maximum_items < 0):
|
||||||
|
logger.error("Invalid maximum items value provided.")
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
# Build the client and connect to the server
|
# Build the client and connect to the server
|
||||||
client = kmip_client.KMIPProxy(config=config, config_file=opts.config_file)
|
client = kmip_client.KMIPProxy(config=config, config_file=opts.config_file)
|
||||||
client.open()
|
client.open()
|
||||||
|
@ -180,7 +189,12 @@ if __name__ == '__main__':
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
result = client.locate(attributes=attributes, credential=credential)
|
result = client.locate(
|
||||||
|
attributes=attributes,
|
||||||
|
offset_items=offset_items,
|
||||||
|
maximum_items=maximum_items,
|
||||||
|
credential=credential
|
||||||
|
)
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
# Display operation results
|
# Display operation results
|
||||||
|
|
|
@ -230,6 +230,22 @@ def build_cli_parser(operation=None):
|
||||||
help="List of attribute names to retrieve, defaults to all "
|
help="List of attribute names to retrieve, defaults to all "
|
||||||
"attributes")
|
"attributes")
|
||||||
elif operation is Operation.LOCATE:
|
elif operation is Operation.LOCATE:
|
||||||
|
parser.add_option(
|
||||||
|
"--offset-items",
|
||||||
|
action="store",
|
||||||
|
type="int",
|
||||||
|
default=None,
|
||||||
|
dest="offset_items",
|
||||||
|
help="The number of matching secrets to skip."
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
"--maximum-items",
|
||||||
|
action="store",
|
||||||
|
type="int",
|
||||||
|
default=None,
|
||||||
|
dest="maximum_items",
|
||||||
|
help="The maximum number of matching secrets to return."
|
||||||
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"-n",
|
"-n",
|
||||||
"--name",
|
"--name",
|
||||||
|
|
|
@ -661,7 +661,7 @@ class ProxyKmipClient(object):
|
||||||
|
|
||||||
@is_connected
|
@is_connected
|
||||||
def locate(self, maximum_items=None, storage_status_mask=None,
|
def locate(self, maximum_items=None, storage_status_mask=None,
|
||||||
object_group_member=None, attributes=None):
|
object_group_member=None, attributes=None, offset_items=None):
|
||||||
"""
|
"""
|
||||||
Search for managed objects, depending on the attributes specified in
|
Search for managed objects, depending on the attributes specified in
|
||||||
the request.
|
the request.
|
||||||
|
@ -669,6 +669,8 @@ class ProxyKmipClient(object):
|
||||||
Args:
|
Args:
|
||||||
maximum_items (integer): Maximum number of object identifiers the
|
maximum_items (integer): Maximum number of object identifiers the
|
||||||
server MAY return.
|
server MAY return.
|
||||||
|
offset_items (integer): Number of object identifiers the server
|
||||||
|
should skip before returning results.
|
||||||
storage_status_mask (integer): A bit mask that indicates whether
|
storage_status_mask (integer): A bit mask that indicates whether
|
||||||
on-line or archived objects are to be searched.
|
on-line or archived objects are to be searched.
|
||||||
object_group_member (ObjectGroupMember): An enumeration that
|
object_group_member (ObjectGroupMember): An enumeration that
|
||||||
|
@ -688,6 +690,9 @@ class ProxyKmipClient(object):
|
||||||
if maximum_items is not None:
|
if maximum_items is not None:
|
||||||
if not isinstance(maximum_items, six.integer_types):
|
if not isinstance(maximum_items, six.integer_types):
|
||||||
raise TypeError("maximum_items must be an integer")
|
raise TypeError("maximum_items must be an integer")
|
||||||
|
if offset_items is not None:
|
||||||
|
if not isinstance(offset_items, six.integer_types):
|
||||||
|
raise TypeError("offset items must be an integer")
|
||||||
if storage_status_mask is not None:
|
if storage_status_mask is not None:
|
||||||
if not isinstance(storage_status_mask, six.integer_types):
|
if not isinstance(storage_status_mask, six.integer_types):
|
||||||
raise TypeError("storage_status_mask must be an integer")
|
raise TypeError("storage_status_mask must be an integer")
|
||||||
|
@ -705,8 +710,12 @@ class ProxyKmipClient(object):
|
||||||
|
|
||||||
# Search for managed objects and handle the results
|
# Search for managed objects and handle the results
|
||||||
result = self.proxy.locate(
|
result = self.proxy.locate(
|
||||||
maximum_items, storage_status_mask,
|
maximum_items=maximum_items,
|
||||||
object_group_member, attributes)
|
offset_items=offset_items,
|
||||||
|
storage_status_mask=storage_status_mask,
|
||||||
|
object_group_member=object_group_member,
|
||||||
|
attributes=attributes
|
||||||
|
)
|
||||||
|
|
||||||
status = result.result_status.value
|
status = result.result_status.value
|
||||||
if status == enums.ResultStatus.SUCCESS:
|
if status == enums.ResultStatus.SUCCESS:
|
||||||
|
|
|
@ -694,11 +694,13 @@ class KMIPProxy(object):
|
||||||
return results[0]
|
return results[0]
|
||||||
|
|
||||||
def locate(self, maximum_items=None, storage_status_mask=None,
|
def locate(self, maximum_items=None, storage_status_mask=None,
|
||||||
object_group_member=None, attributes=None, credential=None):
|
object_group_member=None, attributes=None, credential=None,
|
||||||
|
offset_items=None):
|
||||||
return self._locate(maximum_items=maximum_items,
|
return self._locate(maximum_items=maximum_items,
|
||||||
storage_status_mask=storage_status_mask,
|
storage_status_mask=storage_status_mask,
|
||||||
object_group_member=object_group_member,
|
object_group_member=object_group_member,
|
||||||
attributes=attributes, credential=credential)
|
attributes=attributes, credential=credential,
|
||||||
|
offset_items=offset_items)
|
||||||
|
|
||||||
def query(self, batch=False, query_functions=None, credential=None):
|
def query(self, batch=False, query_functions=None, credential=None):
|
||||||
"""
|
"""
|
||||||
|
@ -1476,12 +1478,14 @@ class KMIPProxy(object):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _locate(self, maximum_items=None, storage_status_mask=None,
|
def _locate(self, maximum_items=None, storage_status_mask=None,
|
||||||
object_group_member=None, attributes=None, credential=None):
|
object_group_member=None, attributes=None, credential=None,
|
||||||
|
offset_items=None):
|
||||||
|
|
||||||
operation = Operation(OperationEnum.LOCATE)
|
operation = Operation(OperationEnum.LOCATE)
|
||||||
|
|
||||||
payload = payloads.LocateRequestPayload(
|
payload = payloads.LocateRequestPayload(
|
||||||
maximum_items=maximum_items,
|
maximum_items=maximum_items,
|
||||||
|
offset_items=offset_items,
|
||||||
storage_status_mask=storage_status_mask,
|
storage_status_mask=storage_status_mask,
|
||||||
object_group_member=object_group_member,
|
object_group_member=object_group_member,
|
||||||
attributes=attributes
|
attributes=attributes
|
||||||
|
|
|
@ -1780,6 +1780,22 @@ class KmipEngine(object):
|
||||||
reverse=True
|
reverse=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Skip the requested offset items and keep the requested maximum items
|
||||||
|
if payload.offset_items is not None:
|
||||||
|
if payload.maximum_items is not None:
|
||||||
|
managed_objects = managed_objects[
|
||||||
|
payload.offset_items:(
|
||||||
|
payload.offset_items + payload.maximum_items
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
managed_objects = managed_objects[payload.offset_items:]
|
||||||
|
else:
|
||||||
|
if payload.maximum_items is not None:
|
||||||
|
managed_objects = managed_objects[:payload.maximum_items]
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
unique_identifiers = [
|
unique_identifiers = [
|
||||||
str(x.unique_identifier) for x in managed_objects
|
str(x.unique_identifier) for x in managed_objects
|
||||||
]
|
]
|
||||||
|
|
|
@ -1477,6 +1477,22 @@ class TestIntegration(testtools.TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(0, len(result.uuids))
|
self.assertEqual(0, len(result.uuids))
|
||||||
|
|
||||||
|
# Test locating keys using offset and maximum item constraints.
|
||||||
|
result = self.client.locate(offset_items=1)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(result.uuids))
|
||||||
|
self.assertIn(uid_a, result.uuids)
|
||||||
|
|
||||||
|
result = self.client.locate(maximum_items=1)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(result.uuids))
|
||||||
|
self.assertIn(uid_b, result.uuids)
|
||||||
|
|
||||||
|
result = self.client.locate(offset_items=1, maximum_items=1)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(result.uuids))
|
||||||
|
self.assertIn(uid_a, result.uuids)
|
||||||
|
|
||||||
# Clean up keys
|
# Clean up keys
|
||||||
result = self.client.destroy(uid_a)
|
result = self.client.destroy(uid_a)
|
||||||
self.assertEqual(ResultStatus.SUCCESS, result.result_status.value)
|
self.assertEqual(ResultStatus.SUCCESS, result.result_status.value)
|
||||||
|
|
|
@ -1132,6 +1132,22 @@ class TestProxyKmipClientIntegration(testtools.TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(0, len(result))
|
self.assertEqual(0, len(result))
|
||||||
|
|
||||||
|
# Test locating keys using offset and maximum item constraints.
|
||||||
|
result = self.client.locate(offset_items=1)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(result))
|
||||||
|
self.assertIn(a_id, result)
|
||||||
|
|
||||||
|
result = self.client.locate(maximum_items=1)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(result))
|
||||||
|
self.assertIn(b_id, result)
|
||||||
|
|
||||||
|
result = self.client.locate(offset_items=1, maximum_items=1)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(result))
|
||||||
|
self.assertIn(a_id, result)
|
||||||
|
|
||||||
# Clean up the keys
|
# Clean up the keys
|
||||||
self.client.destroy(a_id)
|
self.client.destroy(a_id)
|
||||||
self.client.destroy(b_id)
|
self.client.destroy(b_id)
|
||||||
|
|
|
@ -4474,6 +4474,104 @@ class TestKmipEngine(testtools.TestCase):
|
||||||
self.assertIn(id_a, response_payload.unique_identifiers)
|
self.assertIn(id_a, response_payload.unique_identifiers)
|
||||||
self.assertIn(id_b, response_payload.unique_identifiers)
|
self.assertIn(id_b, response_payload.unique_identifiers)
|
||||||
|
|
||||||
|
def test_locate_with_offset_and_maximum_items(self):
|
||||||
|
"""
|
||||||
|
Test locate operation with specified offset and maximum item limits.
|
||||||
|
"""
|
||||||
|
e = engine.KmipEngine()
|
||||||
|
e._data_store = self.engine
|
||||||
|
e._data_store_session_factory = self.session_factory
|
||||||
|
e._data_session = e._data_store_session_factory()
|
||||||
|
e._is_allowed_by_operation_policy = mock.Mock(return_value=True)
|
||||||
|
e._logger = mock.MagicMock()
|
||||||
|
|
||||||
|
key = (
|
||||||
|
b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
)
|
||||||
|
obj_a = pie_objects.SymmetricKey(
|
||||||
|
enums.CryptographicAlgorithm.AES,
|
||||||
|
128,
|
||||||
|
key,
|
||||||
|
name='name1'
|
||||||
|
)
|
||||||
|
obj_a.initial_date = int(time.time())
|
||||||
|
time.sleep(2)
|
||||||
|
obj_b = pie_objects.SymmetricKey(
|
||||||
|
enums.CryptographicAlgorithm.DES,
|
||||||
|
128,
|
||||||
|
key,
|
||||||
|
name='name2'
|
||||||
|
)
|
||||||
|
obj_b.initial_date = int(time.time())
|
||||||
|
time.sleep(2)
|
||||||
|
obj_c = pie_objects.SymmetricKey(
|
||||||
|
enums.CryptographicAlgorithm.AES,
|
||||||
|
128,
|
||||||
|
key,
|
||||||
|
name='name3'
|
||||||
|
)
|
||||||
|
obj_c.initial_date = int(time.time())
|
||||||
|
|
||||||
|
e._data_session.add(obj_a)
|
||||||
|
e._data_session.add(obj_b)
|
||||||
|
e._data_session.add(obj_c)
|
||||||
|
e._data_session.commit()
|
||||||
|
e._data_session = e._data_store_session_factory()
|
||||||
|
|
||||||
|
id_a = str(obj_a.unique_identifier)
|
||||||
|
id_b = str(obj_b.unique_identifier)
|
||||||
|
id_c = str(obj_c.unique_identifier)
|
||||||
|
|
||||||
|
# Locate all objects.
|
||||||
|
payload = payloads.LocateRequestPayload()
|
||||||
|
e._logger.reset_mock()
|
||||||
|
response_payload = e._process_locate(payload)
|
||||||
|
e._data_session.commit()
|
||||||
|
e._data_session = e._data_store_session_factory()
|
||||||
|
|
||||||
|
e._logger.info.assert_any_call("Processing operation: Locate")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[id_c, id_b, id_a],
|
||||||
|
response_payload.unique_identifiers
|
||||||
|
)
|
||||||
|
|
||||||
|
# Locate by skipping the first object and only returning one object.
|
||||||
|
payload = payloads.LocateRequestPayload(
|
||||||
|
offset_items=1,
|
||||||
|
maximum_items=1
|
||||||
|
)
|
||||||
|
e._logger.reset_mock()
|
||||||
|
response_payload = e._process_locate(payload)
|
||||||
|
e._data_session.commit()
|
||||||
|
e._data_session = e._data_store_session_factory()
|
||||||
|
|
||||||
|
e._logger.info.assert_any_call("Processing operation: Locate")
|
||||||
|
|
||||||
|
self.assertEqual([id_b], response_payload.unique_identifiers)
|
||||||
|
|
||||||
|
# Locate by skipping the first two objects.
|
||||||
|
payload = payloads.LocateRequestPayload(offset_items=2)
|
||||||
|
e._logger.reset_mock()
|
||||||
|
response_payload = e._process_locate(payload)
|
||||||
|
e._data_session.commit()
|
||||||
|
e._data_session = e._data_store_session_factory()
|
||||||
|
|
||||||
|
e._logger.info.assert_any_call("Processing operation: Locate")
|
||||||
|
|
||||||
|
self.assertEqual([id_a], response_payload.unique_identifiers)
|
||||||
|
|
||||||
|
# Locate by only returning two objects.
|
||||||
|
payload = payloads.LocateRequestPayload(maximum_items=2)
|
||||||
|
e._logger.reset_mock()
|
||||||
|
response_payload = e._process_locate(payload)
|
||||||
|
e._data_session.commit()
|
||||||
|
e._data_session = e._data_store_session_factory()
|
||||||
|
|
||||||
|
e._logger.info.assert_any_call("Processing operation: Locate")
|
||||||
|
|
||||||
|
self.assertEqual([id_c, id_b], response_payload.unique_identifiers)
|
||||||
|
|
||||||
def test_locate_with_name(self):
|
def test_locate_with_name(self):
|
||||||
"""
|
"""
|
||||||
Test locate operation when 'Name' attribute is given.
|
Test locate operation when 'Name' attribute is given.
|
||||||
|
|
Loading…
Reference in New Issue