2 This library provides methods using IoTDM client libs for testing
3 of communication with IoTDM.
7 # Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
9 # This program and the accompanying materials are made available under the
10 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
11 # and is available at http://www.eclipse.org/legal/epl-v10.html
16 from client_libs.iotdm_it_test_com import IoTDMItCommunicationFactory
17 from client_libs.iotdm_it_test_com import IoTDMJsonPrimitiveBuilder
18 from client_libs.iotdm_it_test_com import RequestAutoHandlingDescriptionBuilder
19 from client_libs.iotdm_it_test_com import RequestAutoHandlingDescription
20 import client_libs.onem2m_http as onem2m_http
21 from client_libs.onem2m_primitive import OneM2M
24 # Mapping of alliases to communication objects
28 def __get_session(allias, session):
32 if allias in __sessions:
33 return __sessions[allias]
38 def prepare_primitive_builder_raw(
39 protocol, primitive_params, content=None, proto_specific_params=None
41 """Creates primitive builder without any default data"""
43 IoTDMJsonPrimitiveBuilder()
44 .set_communication_protocol(protocol)
46 .set_parameters(primitive_params)
47 .set_protocol_specific_parameters(proto_specific_params)
52 def new_primitive_raw(
53 protocol, primitive_params, content=None, proto_specific_params=None
55 """Creates primitive object without any default data"""
56 return prepare_primitive_builder_raw(
57 protocol, primitive_params, content, proto_specific_params
61 def prepare_primitive_builder(
64 proto_specific_params=None,
68 """Creates primitive builder with default data set according communication object used"""
69 communication = __get_session(allias, communication)
72 IoTDMJsonPrimitiveBuilder()
73 .set_communication_protocol(communication.get_protocol())
74 .set_parameters(communication.get_primitive_params())
75 .set_protocol_specific_parameters(communication.get_protocol_params())
79 if communication.get_protocol() == onem2m_http.HTTPPROTOCOLNAME and content:
80 builder.set_proto_param(
81 onem2m_http.http_header_content_length, str(len(content))
84 builder.append_parameters(primitive_params).append_protocol_specific_parameters(
94 proto_specific_params=None,
98 """Creates new primitive object with default data set according communication object used"""
99 return prepare_primitive_builder(
100 primitive_params, content, proto_specific_params, allias, communication
104 def _add_param(params, name, value):
105 if not name or not value:
110 def prepare_request_primitive_builder(
120 Creates builder for request primitive with default data set according
121 communication object used
123 communication = __get_session(allias, communication)
124 if not communication or not target_resource:
125 raise AttributeError("Mandatory attributes not specified")
127 primitive_params = {}
128 _add_param(primitive_params, OneM2M.short_to, target_resource)
129 _add_param(primitive_params, OneM2M.short_operation, operation)
130 _add_param(primitive_params, OneM2M.short_resource_type, resource_type)
131 _add_param(primitive_params, OneM2M.short_result_content, result_content)
133 primitive_params = json.dumps(primitive_params)
135 builder = prepare_primitive_builder(
136 primitive_params, content, communication=communication
141 def new_create_request_primitive(
149 """Creates request primitive for Create operation"""
150 return prepare_request_primitive_builder(
153 operation=OneM2M.operation_create,
154 resource_type=resource_type,
155 result_content=result_content,
157 communication=communication,
161 def new_update_request_primitive(
162 target_resource, content, result_content=None, allias="default", communication=None
164 """Creates request primitive for Update operation"""
165 return prepare_request_primitive_builder(
168 operation=OneM2M.operation_update,
170 result_content=result_content,
172 communication=communication,
176 def new_retrieve_request_primitive(
177 target_resource, result_content=None, allias="default", communication=None
179 """Creates request primitive for Retrieve operation"""
180 return prepare_request_primitive_builder(
183 operation=OneM2M.operation_retrieve,
185 result_content=result_content,
187 communication=communication,
191 def new_delete_request_primitive(
192 target_resource, result_content=None, allias="default", communication=None
194 """Creates request primitive for Delete operation"""
195 return prepare_request_primitive_builder(
198 operation=OneM2M.operation_delete,
199 result_content=result_content,
201 communication=communication,
205 def send_primitive(primitive, allias="default", communication=None):
206 """Sends primitive object using the communication object"""
207 communication = __get_session(allias, communication)
208 rsp = communication.send(primitive)
212 def verify_exchange(request_primitive, response_primitive, status_code=None):
213 """Verifies request and response primitive parameters"""
214 request_primitive.check_exchange(response_primitive, rsc=status_code)
217 def verify_exchange_negative(
218 request_primitive, response_primitive, status_code, error_message=None
220 """Verifies request and error response primitive parameters"""
221 request_primitive.check_exchange_negative(
222 response_primitive, status_code, error_message
226 def verify_request(request_primitive):
227 """Verifies request primitive only"""
228 request_primitive.check_request()
231 def verify_response(response_primitive, rqi=None, rsc=None, request_operation=None):
232 """Verifies response primitive only"""
233 response_primitive.check_response(rqi, rsc, request_operation)
236 def verify_response_negative(
237 response_primitive, rqi=None, rsc=None, error_message=None
239 """Verifies error response primitive only"""
240 response_primitive.check_response_negative(rqi, rsc, error_message)
243 def receive_request_primitive(allias="default", communication=None):
245 Blocking call which receives request primitive. If the request
246 primitive was received, the underlying Rx channel stays blocked
247 until response primitive (related to the request primitive) is
248 provided using respond_response_primitive() method
250 communication = __get_session(allias, communication)
251 req = communication.receive()
255 def respond_response_primitive(
256 response_primitive, allias="default", communication=None
259 Sends response primitive related to the last request primitive received by
260 receive_request_primitive() method
262 communication = __get_session(allias, communication)
263 communication.respond(response_primitive)
266 def create_notification_response(
267 notification_request_primitive, allias="default", communication=None
269 """Creates response primitive for provided notification request primitive"""
270 communication = __get_session(allias, communication)
271 return communication.create_auto_response(
272 notification_request_primitive, OneM2M.result_code_ok
276 def create_notification_response_negative(
277 notification_request_primitive,
283 """Creates negative response primitive for provided notification request primitive"""
284 communication = __get_session(allias, communication)
286 IoTDMJsonPrimitiveBuilder()
287 .set_communication_protocol(communication.get_protocol())
289 OneM2M.short_request_identifier,
290 notification_request_primitive.get_param(OneM2M.short_request_identifier),
292 .set_param(OneM2M.short_response_status_code, result_code)
294 onem2m_http.http_result_code,
295 onem2m_http.onem2m_to_http_result_codes[result_code],
297 .set_content('{"error": "' + error_message + '"}')
299 return builder.build()
302 # JSON pointer strings used by methods providing automatic reply mechanism
303 JSON_POINTER_NOTIFICATION_RN = "/nev/rep/rn"
304 JSON_POINTER_NOTIFICATION_SUR = "/sur"
307 def _on_subscription_create_notificaton_matching_cb(request_primitive):
309 Is used as callback which returns True if the provided request primitive is
310 notification request triggered by creation of new subscription resource.
312 if request_primitive.get_param(OneM2M.short_operation) != OneM2M.operation_notify:
315 if not request_primitive.has_attr(JSON_POINTER_NOTIFICATION_RN):
318 if not request_primitive.has_attr(JSON_POINTER_NOTIFICATION_SUR):
321 rn = request_primitive.get_attr(JSON_POINTER_NOTIFICATION_RN)
322 sur = request_primitive.get_attr(JSON_POINTER_NOTIFICATION_SUR)
329 # Description of such notification request primitive which is received
330 # as result of new subscription resource
331 ON_SUBSCRIPTION_CREATE_DESCRIPTION = RequestAutoHandlingDescription(
335 onem2m_result_code=OneM2M.result_code_ok,
336 matching_cb=_on_subscription_create_notificaton_matching_cb,
340 def _prepare_notification_auto_reply_builder():
342 RequestAutoHandlingDescriptionBuilder()
343 .add_param_criteria(OneM2M.short_operation, OneM2M.operation_notify)
344 .set_onem2m_result_code(OneM2M.result_code_ok)
348 def add_notification_auto_reply_on_subscription_create(
349 allias="default", communication=None
351 """Sets auto reply for notification requests received due to subscription resource creation"""
352 communication = __get_session(allias, communication)
353 communication.add_auto_reply_description(ON_SUBSCRIPTION_CREATE_DESCRIPTION)
356 def remove_notification_auto_reply_on_subscription_create(
357 allias="default", communication=None
359 """Removes auto reply for notification requests received due to subscription resource creation"""
360 communication = __get_session(allias, communication)
361 communication.remove_auto_reply_description(ON_SUBSCRIPTION_CREATE_DESCRIPTION)
364 def get_number_of_auto_replies_on_subscription_create(
365 allias="default", communication=None
367 """Returns number of auto replies on notification requests received when new subscription created"""
368 communication = __get_session(allias, communication)
369 return communication.get_auto_handling_statistics(
370 ON_SUBSCRIPTION_CREATE_DESCRIPTION
374 def verify_number_of_auto_replies_on_subscription_create(
375 replies, allias="default", communication=None
377 """Compares number of auto replies on notifications received when new subscription created"""
378 count = get_number_of_auto_replies_on_subscription_create(allias, communication)
380 raise AssertionError(
381 "Unexpected number of auto replies on subscription create: {}, expected: {}".format(
387 __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING = {}
390 def add_auto_reply_to_notification_from_subscription(
391 subscription_resource_id, allias="default", communication=None
394 Sets auto reply for notifications from specific subscription resource
395 identified by its CSE-relative resource ID
397 communication = __get_session(allias, communication)
398 builder = _prepare_notification_auto_reply_builder()
399 if subscription_resource_id in __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING:
401 "Auto reply for subscription resource {} already set".format(
402 subscription_resource_id
406 builder.add_content_criteria(
407 JSON_POINTER_NOTIFICATION_SUR, subscription_resource_id
409 new_description = builder.build()
410 __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING[
411 subscription_resource_id
413 communication.add_auto_reply_description(new_description)
416 def remove_auto_reply_to_notification_from_subscription(
417 subscription_resource_id, allias="default", communication=None
419 """Removes auto reply for specific subscription identified by its CSE-relative resource ID"""
420 communication = __get_session(allias, communication)
421 description = __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING[
422 subscription_resource_id
426 "No auto reply set for specific subscription resource: {}".format(
427 subscription_resource_id
430 communication.remove_auto_reply_description(description)
433 def get_number_of_auto_replies_to_notifications_from_subscription(
434 subscription_resource_id, allias="default", communication=None
437 Returns number of automatic replies for specific subscription resource
438 identified by its CSE-relative resource ID
440 communication = __get_session(allias, communication)
441 description = __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING[
442 subscription_resource_id
446 "No auto reply set for specific subscription resource: {}".format(
447 subscription_resource_id
450 return communication.get_auto_handling_statistics(description).counter
453 def verify_number_of_auto_replies_to_notification_from_subscription(
454 subscription_resource_id, replies, allias="default", communication=None
457 Compares number of automatic replies for specific subscription resource
458 identified by its CSE-relative resource ID
460 count = get_number_of_auto_replies_to_notifications_from_subscription(
461 subscription_resource_id, allias, communication
464 raise AssertionError(
466 "Unexpected number of auto replies to notification from subscription {}, "
467 + "auto replies: {}, expected: {}"
468 ).format(subscription_resource_id, count, replies)
472 # Primitive getters uses JSON pointer object or string
473 # to identify specific parameter/attribute/protocol_specific_parameter
474 def get_primitive_content(primitive):
475 return primitive.get_content_str()
478 def get_primitive_content_attribute(primitive, pointer):
479 return primitive.get_attr(pointer)
482 def get_primitive_parameters(primitive):
483 return primitive.get_parameters_str()
486 def get_primitive_param(primitive, pointer):
487 return primitive.get_param(pointer)
490 def get_primitive_protocol_specific_parameters(primitive):
491 return primitive.get_protocol_specific_parameters_str()
494 def get_primitive_protocol_specific_param(primitive, pointer):
495 return primitive.get_proto_param(pointer)
499 def close_iotdm_communication(allias="default", communication=None):
500 """Closes communication identified by allias or provided as object"""
501 communication = __get_session(allias, communication)
503 if allias in __sessions:
504 del __sessions[allias]
507 def create_iotdm_communication(
508 entity_id, protocol, protocol_params=None, rx_port=None, allias="default"
511 Creates communication object and starts the communication.
512 :param entity_id: ID which will be used in From parameter of request primitives
513 :param protocol: Communication protocol to use for communication
514 :param protocol_params: Default protocol specific parameters to be used
515 :param rx_port: Local Rx port number
516 :param allias: Allias to be assigned to the created communication object
517 :return: The new communication object
519 if protocol == onem2m_http.HTTPPROTOCOLNAME:
520 conn = IoTDMItCommunicationFactory().create_http_json_primitive_communication(
521 entity_id, protocol, protocol_params, rx_port
524 raise RuntimeError("Unsupported protocol: {}".format(protocol))
528 __sessions[allias] = conn
532 def get_local_ip_from_list(iotdm_ip, local_ip_list_str):
534 Looks for longest prefix matching local interface IP address with the
536 :param iotdm_ip: IP address of IoTDM
537 :param local_ip_list_str: String of IP address of local interfaces separated with space
538 :return: The longed prefix matching IP or the first one
540 if not local_ip_list_str:
541 raise RuntimeError("Local IP address not provided")
544 raise RuntimeError("IoTDM IP address not provided")
546 ip_list = local_ip_list_str.split(" ")
548 if len(ip_list) == 1:
551 for i in range(len(iotdm_ip), 0, -1):
552 # TODO this is not real longest prefix match
555 if ip.startswith(iotdm_ip[0:i]):
558 # no match, just choose the first one
563 def create_http_default_communication_parameters(address, port, content_type):
564 """Returns JSON string including default HTTP specific parameters"""
565 return '{{"{}": "{}", "{}": {}, "Content-Type": "{}"}}'.format(
566 onem2m_http.protocol_address,
568 onem2m_http.protocol_port,
574 def create_iotdm_http_connection(
575 entity_id, address, port, content_type, rx_port=None, allias="default"
577 """Creates HTTP communication"""
578 default_params = create_http_default_communication_parameters(
579 address, port, content_type
581 return create_iotdm_communication(
582 entity_id, "http", default_params, rx_port, allias