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(protocol, primitive_params, content=None, proto_specific_params=None):
39 """Creates primitive builder without any default data"""
40 builder = IoTDMJsonPrimitiveBuilder()\
41 .set_communication_protocol(protocol)\
42 .set_content(content)\
43 .set_parameters(primitive_params)\
44 .set_protocol_specific_parameters(proto_specific_params)
48 def new_primitive_raw(protocol, primitive_params, content=None, proto_specific_params=None):
49 """Creates primitive object without any default data"""
50 return prepare_primitive_builder_raw(protocol, primitive_params, content, proto_specific_params).build()
53 def prepare_primitive_builder(primitive_params, content=None, proto_specific_params=None,
54 allias="default", communication=None):
55 """Creates primitive builder with default data set according communication object used"""
56 communication = __get_session(allias, communication)
58 builder = IoTDMJsonPrimitiveBuilder()\
59 .set_communication_protocol(communication.get_protocol())\
60 .set_parameters(communication.get_primitive_params())\
61 .set_protocol_specific_parameters(communication.get_protocol_params())\
64 if communication.get_protocol() == onem2m_http.HTTPPROTOCOLNAME and content:
65 builder.set_proto_param(onem2m_http.http_header_content_length, str(len(content)))
67 builder.append_parameters(primitive_params)\
68 .append_protocol_specific_parameters(proto_specific_params)
73 def new_primitive(primitive_params, content=None, proto_specific_params=None,
74 allias="default", communication=None):
75 """Creates new primitive object with default data set according communication object used"""
76 return prepare_primitive_builder(primitive_params, content, proto_specific_params, allias, communication).build()
79 def _add_param(params, name, value):
80 if not name or not value:
85 def prepare_request_primitive_builder(target_resource, content=None, operation=None, resource_type=None,
86 result_content=None, allias="default", communication=None):
88 Creates builder for request primitive with default data set according
89 communication object used
91 communication = __get_session(allias, communication)
92 if not communication or not target_resource:
93 raise AttributeError("Mandatory attributes not specified")
96 _add_param(primitive_params, OneM2M.short_to, target_resource)
97 _add_param(primitive_params, OneM2M.short_operation, operation)
98 _add_param(primitive_params, OneM2M.short_resource_type, resource_type)
99 _add_param(primitive_params, OneM2M.short_result_content, result_content)
101 primitive_params = json.dumps(primitive_params)
103 builder = prepare_primitive_builder(primitive_params, content, communication=communication)
107 def new_create_request_primitive(target_resource, content, resource_type, result_content=None,
108 allias="default", communication=None):
109 """Creates request primitive for Create operation"""
110 return prepare_request_primitive_builder(target_resource, content, operation=OneM2M.operation_create,
111 resource_type=resource_type, result_content=result_content,
112 allias=allias, communication=communication).build()
115 def new_update_request_primitive(target_resource, content, result_content=None, allias="default", communication=None):
116 """Creates request primitive for Update operation"""
117 return prepare_request_primitive_builder(target_resource, content, operation=OneM2M.operation_update,
118 resource_type=None, result_content=result_content,
119 allias=allias, communication=communication).build()
122 def new_retrieve_request_primitive(target_resource, result_content=None, allias="default", communication=None):
123 """Creates request primitive for Retrieve operation"""
124 return prepare_request_primitive_builder(target_resource, content=None,
125 operation=OneM2M.operation_retrieve,
126 resource_type=None, result_content=result_content,
127 allias=allias, communication=communication).build()
130 def new_delete_request_primitive(target_resource, result_content=None, allias="default", communication=None):
131 """Creates request primitive for Delete operation"""
132 return prepare_request_primitive_builder(target_resource, content=None,
133 operation=OneM2M.operation_delete, result_content=result_content,
134 allias=allias, communication=communication).build()
137 def send_primitive(primitive, allias="default", communication=None):
138 """Sends primitive object using the communication object"""
139 communication = __get_session(allias, communication)
140 rsp = communication.send(primitive)
144 def verify_exchange(request_primitive, response_primitive, status_code=None):
145 """Verifies request and response primitive parameters"""
146 request_primitive.check_exchange(response_primitive, rsc=status_code)
149 def verify_exchange_negative(request_primitive, response_primitive, status_code, error_message=None):
150 """Verifies request and error response primitive parameters"""
151 request_primitive.check_exchange_negative(response_primitive, status_code, error_message)
154 def verify_request(request_primitive):
155 """Verifies request primitive only"""
156 request_primitive.check_request()
159 def verify_response(response_primitive, rqi=None, rsc=None, request_operation=None):
160 """Verifies response primitive only"""
161 response_primitive.check_response(rqi, rsc, request_operation)
164 def verify_response_negative(response_primitive, rqi=None, rsc=None, error_message=None):
165 """Verifies error response primitive only"""
166 response_primitive.check_response_negative(rqi, rsc, error_message)
169 def receive_request_primitive(allias="default", communication=None):
171 Blocking call which receives request primitive. If the request
172 primitive was received, the underlying Rx channel stays blocked
173 until response primitive (related to the request primitive) is
174 provided using respond_response_primitive() method
176 communication = __get_session(allias, communication)
177 req = communication.receive()
181 def respond_response_primitive(response_primitive, allias="default", communication=None):
183 Sends response primitive related to the last request primitive received by
184 receive_request_primitive() method
186 communication = __get_session(allias, communication)
187 communication.respond(response_primitive)
190 def create_notification_response(notification_request_primitive, allias="default", communication=None):
191 """Creates response primitive for provided notification request primitive"""
192 communication = __get_session(allias, communication)
193 return communication.create_auto_response(notification_request_primitive,
194 OneM2M.result_code_ok)
197 def create_notification_response_negative(notification_request_primitive, result_code, error_message,
198 allias="default", communication=None):
199 """Creates negative response primitive for provided notification request primitive"""
200 communication = __get_session(allias, communication)
201 builder = IoTDMJsonPrimitiveBuilder() \
202 .set_communication_protocol(communication.get_protocol()) \
203 .set_param(OneM2M.short_request_identifier,
204 notification_request_primitive.get_param(OneM2M.short_request_identifier)) \
205 .set_param(OneM2M.short_response_status_code, result_code) \
206 .set_proto_param(onem2m_http.http_result_code, onem2m_http.onem2m_to_http_result_codes[result_code])\
207 .set_content('{"error": "' + error_message + '"}')
208 return builder.build()
211 # JSON pointer strings used by methods providing automatic reply mechanism
212 JSON_POINTER_NOTIFICATION_RN = "/nev/rep/rn"
213 JSON_POINTER_NOTIFICATION_SUR = "/sur"
216 def _on_subscription_create_notificaton_matching_cb(request_primitive):
218 Is used as callback which returns True if the provided request primitive is
219 notification request triggered by creation of new subscription resource.
221 if request_primitive.get_param(OneM2M.short_operation) != OneM2M.operation_notify:
224 if not request_primitive.has_attr(JSON_POINTER_NOTIFICATION_RN):
227 if not request_primitive.has_attr(JSON_POINTER_NOTIFICATION_SUR):
230 rn = request_primitive.get_attr(JSON_POINTER_NOTIFICATION_RN)
231 sur = request_primitive.get_attr(JSON_POINTER_NOTIFICATION_SUR)
238 # Description of such notification request primitive which is received
239 # as result of new subscription resource
240 ON_SUBSCRIPTION_CREATE_DESCRIPTION =\
241 RequestAutoHandlingDescription(None, None, None,
242 onem2m_result_code=OneM2M.result_code_ok,
243 matching_cb=_on_subscription_create_notificaton_matching_cb)
246 def _prepare_notification_auto_reply_builder():
247 return RequestAutoHandlingDescriptionBuilder()\
248 .add_param_criteria(OneM2M.short_operation, OneM2M.operation_notify)\
249 .set_onem2m_result_code(OneM2M.result_code_ok)
252 def add_notification_auto_reply_on_subscription_create(allias="default", communication=None):
253 """Sets auto reply for notification requests received due to subscription resource creation"""
254 communication = __get_session(allias, communication)
255 communication.add_auto_reply_description(ON_SUBSCRIPTION_CREATE_DESCRIPTION)
258 def remove_notification_auto_reply_on_subscription_create(allias="default", communication=None):
259 """Removes auto reply for notification requests received due to subscription resource creation"""
260 communication = __get_session(allias, communication)
261 communication.remove_auto_reply_description(ON_SUBSCRIPTION_CREATE_DESCRIPTION)
264 def get_number_of_auto_replies_on_subscription_create(allias="default", communication=None):
265 """Returns number of auto replies on notification requests received when new subscription created"""
266 communication = __get_session(allias, communication)
267 return communication.get_auto_handling_statistics(ON_SUBSCRIPTION_CREATE_DESCRIPTION).counter
270 def verify_number_of_auto_replies_on_subscription_create(replies, allias="default", communication=None):
271 """Compares number of auto replies on notifications received when new subscription created"""
272 count = get_number_of_auto_replies_on_subscription_create(allias, communication)
274 raise AssertionError("Unexpected number of auto replies on subscription create: {}, expected: {}".format(
278 __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING = {}
281 def add_auto_reply_to_notification_from_subscription(subscription_resource_id, allias="default", communication=None):
283 Sets auto reply for notifications from specific subscription resource
284 identified by its CSE-relative resource ID
286 communication = __get_session(allias, communication)
287 builder = _prepare_notification_auto_reply_builder()
288 if subscription_resource_id in __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING:
289 raise RuntimeError("Auto reply for subscription resource {} already set".format(subscription_resource_id))
291 builder.add_content_criteria(JSON_POINTER_NOTIFICATION_SUR, subscription_resource_id)
292 new_description = builder.build()
293 __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING[subscription_resource_id] = new_description
294 communication.add_auto_reply_description(new_description)
297 def remove_auto_reply_to_notification_from_subscription(subscription_resource_id, allias="default", communication=None):
298 """Removes auto reply for specific subscription identified by its CSE-relative resource ID"""
299 communication = __get_session(allias, communication)
300 description = __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING[subscription_resource_id]
302 raise RuntimeError("No auto reply set for specific subscription resource: {}".format(subscription_resource_id))
303 communication.remove_auto_reply_description(description)
306 def get_number_of_auto_replies_to_notifications_from_subscription(subscription_resource_id,
307 allias="default", communication=None):
309 Returns number of automatic replies for specific subscription resource
310 identified by its CSE-relative resource ID
312 communication = __get_session(allias, communication)
313 description = __SUBSCRIPTION_RESOURCE_ID_DESCRIPTION_MAPPING[subscription_resource_id]
315 raise RuntimeError("No auto reply set for specific subscription resource: {}".format(subscription_resource_id))
316 return communication.get_auto_handling_statistics(description).counter
319 def verify_number_of_auto_replies_to_notification_from_subscription(subscription_resource_id, replies,
320 allias="default", communication=None):
322 Compares number of automatic replies for specific subscription resource
323 identified by its CSE-relative resource ID
325 count = get_number_of_auto_replies_to_notifications_from_subscription(subscription_resource_id,
326 allias, communication)
328 raise AssertionError(("Unexpected number of auto replies to notification from subscription {}, " +
329 "auto replies: {}, expected: {}").format(subscription_resource_id, count, replies))
332 # Primitive getters uses JSON pointer object or string
333 # to identify specific parameter/attribute/protocol_specific_parameter
334 def get_primitive_content(primitive):
335 return primitive.get_content_str()
338 def get_primitive_content_attribute(primitive, pointer):
339 return primitive.get_attr(pointer)
342 def get_primitive_parameters(primitive):
343 return primitive.get_parameters_str()
346 def get_primitive_param(primitive, pointer):
347 return primitive.get_param(pointer)
350 def get_primitive_protocol_specific_parameters(primitive):
351 return primitive.get_protocol_specific_parameters_str()
354 def get_primitive_protocol_specific_param(primitive, pointer):
355 return primitive.get_proto_param(pointer)
359 def close_iotdm_communication(allias="default", communication=None):
360 """Closes communication identified by allias or provided as object"""
361 communication = __get_session(allias, communication)
363 if allias in __sessions:
364 del __sessions[allias]
367 def create_iotdm_communication(entity_id, protocol, protocol_params=None, rx_port=None, allias="default"):
369 Creates communication object and starts the communication.
370 :param entity_id: ID which will be used in From parameter of request primitives
371 :param protocol: Communication protocol to use for communication
372 :param protocol_params: Default protocol specific parameters to be used
373 :param rx_port: Local Rx port number
374 :param allias: Allias to be assigned to the created communication object
375 :return: The new communication object
377 if protocol == onem2m_http.HTTPPROTOCOLNAME:
378 conn = IoTDMItCommunicationFactory().create_http_json_primitive_communication(entity_id, protocol,
379 protocol_params, rx_port)
381 raise RuntimeError("Unsupported protocol: {}".format(protocol))
385 __sessions[allias] = conn
389 def get_local_ip_from_list(iotdm_ip, local_ip_list_str):
391 Looks for longest prefix matching local interface IP address with the
393 :param iotdm_ip: IP address of IoTDM
394 :param local_ip_list_str: String of IP address of local interfaces separated with space
395 :return: The longed prefix matching IP or the first one
397 if not local_ip_list_str:
398 raise RuntimeError("Local IP address not provided")
401 raise RuntimeError("IoTDM IP address not provided")
403 ip_list = local_ip_list_str.split(" ")
405 if len(ip_list) == 1:
408 for i in range(len(iotdm_ip), 0, -1):
409 # TODO this is not real longest prefix match
412 if ip.startswith(iotdm_ip[0: i]):
415 # no match, just choose the first one
420 def create_http_default_communication_parameters(address, port, content_type):
421 """Returns JSON string including default HTTP specific parameters"""
422 return '{{"{}": "{}", "{}": {}, "Content-Type": "{}"}}'.format(
423 onem2m_http.protocol_address, address,
424 onem2m_http.protocol_port, port,
428 def create_iotdm_http_connection(entity_id, address, port, content_type, rx_port=None, allias="default"):
429 """Creates HTTP communication"""
430 default_params = create_http_default_communication_parameters(address, port, content_type)
431 return create_iotdm_communication(entity_id, "http", default_params, rx_port, allias)