Migrate 030_bgp_functional_evpn.robot
[integration/test.git] / csit / libraries / TemplatedRequests.robot
1 *** Settings ***
2 Documentation       Resource for supporting http Requests based on data stored in files.
3 ...
4 ...                 Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
5 ...
6 ...                 This program and the accompanying materials are made available under the
7 ...                 terms of the Eclipse Public License v1.0 which accompanies this distribution,
8 ...                 and is available at http://www.eclipse.org/legal/epl-v10.html
9 ...
10 ...
11 ...                 The main strength of this library are *_As_*_Templated keywords
12 ...                 User gives a path to directory where files with templates for URI
13 ...                 and XML (or JSON) data are present, and a mapping with substitution to make;
14 ...                 the keywords will take it from there.
15 ...                 Mapping can be given as a dict object, or as its json text representation.
16 ...                 Simple example (tidy insists on single space where 4 spaces should be):
17 ...                 TemplatedRequests.Put_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"}
18 ...                 TemplatedRequests.Get_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"} verify=True
19 ...
20 ...                 In that example, we are PUTting "person" data with specified value for "NAME" placeholder.
21 ...                 We are not verifying PUT response (probably empty string which is not a valid JSON),
22 ...                 but we are issuing GET (same URI) and verifying the repsonse matches the same data.
23 ...                 Both lines are returning text response, but in the example we are not saving it into variable.
24 ...
25 ...                 Optionally, *_As_*_Templated keywords call verification of response.
26 ...                 There are separate Verify_* keywords, for users who use intermediate processing.
27 ...                 For JSON responses, there is a support for normalizing.
28 ...                 *_Templated keywords without As allow more customization at cost of more arguments.
29 ...                 *_Uri keywords do not use templates, but may be useful in general,
30 ...                 perhaps for users who call Resolve_Text_* keywords.
31 ...                 *_As_*_Uri are the less flexible but less argument-heavy versions of *_Uri keywords.
32 ...
33 ...                 This resource supports generating data with simple lists.
34 ...                 ${iterations} argument control number of items, "$i" will be substituted
35 ...                 automatically (not by the provided mapping) with integers starting with ${iter_start} (default 1).
36 ...                 For example "iterations=2 iter_start=3" will create items with i=3 and i=4.
37 ...
38 ...                 This implementation relies on file names to distinguish data.
39 ...                 Each file is expected to end in newline, compiled data has final newline removed.
40 ...                 Here is a table so that users can create their own templates:
41 ...                 location.uri: Template with URI.
42 ...                 data.xml: Template with XML data to send, or GET data to expect.
43 ...                 data.json: Template with JSON data to send, or GET data to expect.
44 ...                 post_data.xml: Template with XML data to POST, (different from GET response).
45 ...                 post_data.json: Template with JSON data to POST, (different from GET response).
46 ...                 response.xml: Template with PUT or POST XML response to expect.
47 ...                 response.json: Template with PUT or POST JSON response to expect.
48 ...                 *.prolog.*: Temlate with data before iterated items.
49 ...                 *.item.*: Template with data piece corresponding to one item.
50 ...                 *.epilog.*: Temlate with data after iterated items.
51 ...
52 ...                 One typical use of this Resource is to make runtime changes to ODL configuration.
53 ...                 Different ODL parts have varying ways of configuration,
54 ...                 this library affects only the Config Subsystem way.
55 ...                 Config Subsystem has (except for Java APIs mostly available only from inside of ODL)
56 ...                 a NETCONF server as its publicly available entry point.
57 ...                 Netconf-connector feature makes this netconf server available for RESTCONF calls.
58 ...                 Be careful to use appropriate feature, odl-netconf-connector* does not work in cluster.
59 ...
60 ...                 This Resource currently calls RequestsLibrary directly,
61 ...                 so it does not work with AuthStandalone or similar.
62 ...                 This Resource does not maintain any internal Sessions.
63 ...                 If caller does not provide any, session with alias "default" is used.
64 ...                 There is a helper Keyword to create the "default" session.
65 ...                 The session used is assumed to have most things pre-configured appropriately,
66 ...                 which includes auth, host, port and (lack of) base URI.
67 ...                 It is recommended to have everything past port (for example /restconf) be defined
68 ...                 not in the session, but in URI data of individual templates.
69 ...                 Headers are set in Keywords appropriately. Http session's timout is configurable
70 ...                 both on session level (where it becomes a default value for requests) and on request
71 ...                 level (when present, it overrides the session value). To override the default
72 ...                 value keywords' http_timeout parameter may be used.
73 ...
74 ...                 These Keywords contain frequent BuiltIn.Log invocations,
75 ...                 so they are not suited for scale or performance suites.
76 ...                 And as usual, performance tests should use specialized utilities,
77 ...                 as Robot in general and this Resource specifically will be too slow.
78 ...
79 ...                 As this Resource makes assumptions about intended headers,
80 ...                 it is not flexible enough for suites specifically testing Restconf corner cases.
81 ...                 Also, list of allowed http status codes is quite rigid and broad.
82 ...
83 ...                 Rules for ordering Keywords within this Resource:
84 ...                 1. User friendlier Keywords first.
85 ...                 2. Get, Put, Post, Delete, Verify.
86 ...                 3. Within class of equally usable, use order in which a suite would call them.
87 ...                 4. Higher-level Keywords first.
88 ...                 5. Json before Xml.
89 ...                 Motivation: Users read from the start, so it is important
90 ...                 to offer them the better-to-use Keywords first.
91 ...                 https://wiki.opendaylight.org/view/Integration/Test/Test_Code_Guidelines#Keyword_ordering
92 ...                 In this case, templates are nicer that raw data,
93 ...                 *_As_* keywords are better than messing wth explicit header dicts,
94 ...                 Json is less prone to element ordering issues.
95 ...                 PUT does not fail on existing element, also it does not allow
96 ...                 shortened URIs (container instead keyed list element) as Post does.
97 ...
98 ...                 TODO: Add ability to override allowed status codes,
99 ...                 so that negative tests do not need to parse the failure message.
100 ...
101 ...                 TODO: Migrate suites to this Resource and remove *ViaRestconf Resources.
102 ...
103 ...                 TODO: Currently the verification step is only in *_As_*_Templated keywords.
104 ...                 It could be moved to "non-as" *_Templated ones,
105 ...                 but that would take even more horizontal space. Is it worth doing?
106 ...
107 ...                 TODO: Should iterations=0 be supported for JSON (remove [])?
108 ...
109 ...                 TODO: Currently, ${ACCEPT_EMPTY} is used for JSON-expecting requests.
110 ...                 perhaps explicit ${ACCEPT_JSON} will be better, even if it sends few bytes more?
111
112 Library             Collections
113 Library             OperatingSystem
114 Library             String
115 Library             RequestsLibrary
116 Library             ${CURDIR}/norm_json.py
117 Resource            ${CURDIR}/../variables/Variables.robot
118
119
120 *** Variables ***
121 # TODO: Make the following list more narrow when streams without Bug 2594 fix (up to beryllium) are no longer used.
122 # List of integers, not strings. Used by DELETE if the resource may be not present.
123 @{ALLOWED_DELETE_STATUS_CODES}
124 ...                                 ${200}
125 ...                                 ${201}
126 ...                                 ${204}
127 ...                                 ${404}
128 ...                                 ${409}
129 # List of integers, not strings. Used by both PUT and DELETE (if the resource should have been present).
130 @{ALLOWED_STATUS_CODES}
131 ...                                 ${200}
132 ...                                 ${201}
133 ...                                 ${204}
134 @{DATA_VALIDATION_ERROR}            ${400}    # For testing mildly negative scenarios where ODL reports user error.
135 # List of integers, not strings. Used by DELETE if the resource may be not present.
136 @{DELETED_STATUS_CODES}
137 ...                                 ${404}
138 ...                                 ${409}
139 # TODO: Add option for delete to require 404.
140 @{INTERNAL_SERVER_ERROR}            ${500}    # Only for testing severely negative scenarios where ODL cannot recover.
141 @{KEYS_WITH_BITS}                   op    # the default list with keys to be sorted when norm_json libray is used
142 @{NO_STATUS_CODES}
143 # List of integers, not strings. Used in Keystone Authentication when the user is not authorized to use the requested resource.
144 @{UNAUTHORIZED_STATUS_CODES}
145 ...                                 ${401}
146
147
148 *** Keywords ***
149 Create_Default_Session
150     [Documentation]    Create "default" session to ${url} with authentication and connection parameters.
151     ...    This Keyword is in this Resource only so that user do not need to call RequestsLibrary directly.
152     [Arguments]    ${url}=http://${ODL_SYSTEM_IP}:${RESTCONFPORT}    ${auth}=${AUTH}    ${timeout}=${DEFAULT_TIMEOUT_HTTP}    ${max_retries}=0
153     RequestsLibrary.Create_Session
154     ...    alias=default
155     ...    url=${url}
156     ...    auth=${auth}
157     ...    timeout=${timeout}
158     ...    max_retries=${max_retries}
159
160 Get_As_Json_Templated
161     [Documentation]    Add arguments sensible for JSON data, return Get_Templated response text.
162     ...    Optionally, verification against JSON data (may be iterated) is called.
163     ...    Only subset of JSON data is verified and returned if JMES path is specified in
164     ...    file ${folder}${/}jmespath.expr.
165     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
166     ...    ${http_timeout}=${EMPTY}    ${log_response}=True    ${iter_j_offset}=0
167     ${response_text} =    Get_Templated
168     ...    folder=${folder}
169     ...    mapping=${mapping}
170     ...    accept=${ACCEPT_EMPTY}
171     ...    session=${session}
172     ...    normalize_json=True
173     ...    http_timeout=${http_timeout}
174     ...    log_response=${log_response}
175     IF    ${verify}
176         Verify_Response_As_Json_Templated
177         ...    response=${response_text}
178         ...    folder=${folder}
179         ...    base_name=data
180         ...    mapping=${mapping}
181         ...    iterations=${iterations}
182         ...    iter_start=${iter_start}
183         ...    iter_j_offset=${iter_j_offset}
184     END
185     RETURN    ${response_text}
186
187 Get_As_Xml_Templated
188     [Documentation]    Add arguments sensible for XML data, return Get_Templated response text.
189     ...    Optionally, verification against XML data (may be iterated) is called.
190     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
191     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
192     ${response_text} =    Get_Templated
193     ...    folder=${folder}
194     ...    mapping=${mapping}
195     ...    accept=${ACCEPT_XML}
196     ...    session=${session}
197     ...    normalize_json=False
198     ...    http_timeout=${http_timeout}
199     IF    ${verify}
200         Verify_Response_As_Xml_Templated
201         ...    response=${response_text}
202         ...    folder=${folder}
203         ...    base_name=data
204         ...    mapping=${mapping}
205         ...    iterations=${iterations}
206         ...    iter_start=${iter_start}
207         ...    iter_j_offset=${iter_j_offset}
208     END
209     RETURN    ${response_text}
210
211 Put_As_Json_Templated
212     [Documentation]    Add arguments sensible for JSON data, return Put_Templated response text.
213     ...    Optionally, verification against response.json (no iteration) is called.
214     ...    Only subset of JSON data is verified and returned if JMES path is specified in
215     ...    file ${folder}${/}jmespath.expr.
216     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
217     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
218     ${response_text} =    Put_Templated
219     ...    folder=${folder}
220     ...    base_name=data
221     ...    extension=json
222     ...    accept=${ACCEPT_EMPTY}
223     ...    content_type=${HEADERS_YANG_JSON}
224     ...    mapping=${mapping}
225     ...    session=${session}
226     ...    normalize_json=True
227     ...    endline=${\n}
228     ...    iterations=${iterations}
229     ...    iter_start=${iter_start}
230     ...    http_timeout=${http_timeout}
231     ...    iter_j_offset=${iter_j_offset}
232     IF    ${verify}
233         Verify_Response_As_Json_Templated
234         ...    response=${response_text}
235         ...    folder=${folder}
236         ...    base_name=response
237         ...    mapping=${mapping}
238         ...    iter_j_offset=${iter_j_offset}
239     END
240     RETURN    ${response_text}
241
242 Put_As_Xml_Templated
243     [Documentation]    Add arguments sensible for XML data, return Put_Templated response text.
244     ...    Optionally, verification against response.xml (no iteration) is called.
245     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
246     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
247     # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
248     ${response_text} =    Put_Templated
249     ...    folder=${folder}
250     ...    base_name=data
251     ...    extension=xml
252     ...    accept=${ACCEPT_XML}
253     ...    content_type=${HEADERS_XML}
254     ...    mapping=${mapping}
255     ...    session=${session}
256     ...    normalize_json=False
257     ...    endline=${\n}
258     ...    iterations=${iterations}
259     ...    iter_start=${iter_start}
260     ...    http_timeout=${http_timeout}
261     ...    iter_j_offset=${iter_j_offset}
262     IF    ${verify}
263         Verify_Response_As_Xml_Templated
264         ...    response=${response_text}
265         ...    folder=${folder}
266         ...    base_name=response
267         ...    mapping=${mapping}
268         ...    iter_j_offset=${iter_j_offset}
269     END
270     RETURN    ${response_text}
271
272 Post_As_Json_Templated
273     [Documentation]    Add arguments sensible for JSON data, return Post_Templated response text.
274     ...    Optionally, verification against response.json (no iteration) is called.
275     ...    Only subset of JSON data is verified and returned if JMES path is specified in
276     ...    file ${folder}${/}jmespath.expr.
277     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
278     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
279     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
280     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
281     ${response_text} =    Post_Templated
282     ...    folder=${folder}
283     ...    base_name=data
284     ...    extension=json
285     ...    accept=${ACCEPT_EMPTY}
286     ...    content_type=${HEADERS_YANG_JSON}
287     ...    mapping=${mapping}
288     ...    session=${session}
289     ...    normalize_json=True
290     ...    endline=${\n}
291     ...    iterations=${iterations}
292     ...    iter_start=${iter_start}
293     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
294     ...    explicit_status_codes=${explicit_status_codes}
295     ...    http_timeout=${http_timeout}
296     ...    iter_j_offset=${iter_j_offset}
297     IF    ${verify}
298         Verify_Response_As_Json_Templated
299         ...    response=${response_text}
300         ...    folder=${folder}
301         ...    base_name=response
302         ...    mapping=${mapping}
303         ...    iter_j_offset=${iter_j_offset}
304     END
305     RETURN    ${response_text}
306
307 Post_As_Json_Rfc8040_Templated
308     [Documentation]    Add arguments sensible for JSON data, return Post_Templated response text.
309     ...    Optionally, verification against response.json (no iteration) is called.
310     ...    Only subset of JSON data is verified and returned if JMES path is specified in
311     ...    file ${folder}${/}jmespath.expr.
312     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
313     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
314     ...    RFC8040 defines RESTCONF protocol, for configuring data defined in YANG version 1
315     ...    or YANG version 1.1, using the datastore concepts defined in NETCONF.
316     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
317     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
318     ${response_text} =    Post_Templated
319     ...    folder=${folder}
320     ...    base_name=data
321     ...    extension=json
322     ...    accept=${ACCEPT_EMPTY}
323     ...    content_type=${HEADERS_YANG_RFC8040_JSON}
324     ...    mapping=${mapping}
325     ...    session=${session}
326     ...    normalize_json=True
327     ...    endline=${\n}
328     ...    iterations=${iterations}
329     ...    iter_start=${iter_start}
330     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
331     ...    explicit_status_codes=${explicit_status_codes}
332     ...    http_timeout=${http_timeout}
333     ...    iter_j_offset=${iter_j_offset}
334     IF    ${verify}
335         Verify_Response_As_Json_Templated
336         ...    response=${response_text}
337         ...    folder=${folder}
338         ...    base_name=response
339         ...    mapping=${mapping}
340         ...    iter_j_offset=${iter_j_offset}
341     END
342     RETURN    ${response_text}
343
344 Post_As_Xml_Templated
345     [Documentation]    Add arguments sensible for XML data, return Post_Templated response text.
346     ...    Optionally, verification against response.xml (no iteration) is called.
347     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
348     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
349     # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
350     ${response_text} =    Post_Templated
351     ...    folder=${folder}
352     ...    base_name=data
353     ...    extension=xml
354     ...    accept=${ACCEPT_XML}
355     ...    content_type=${HEADERS_XML}
356     ...    mapping=${mapping}
357     ...    session=${session}
358     ...    normalize_json=False
359     ...    endline=${\n}
360     ...    iterations=${iterations}
361     ...    iter_start=${iter_start}
362     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
363     ...    explicit_status_codes=${explicit_status_codes}
364     ...    http_timeout=${http_timeout}
365     ...    iter_j_offset=${iter_j_offset}
366     IF    ${verify}
367         Verify_Response_As_Xml_Templated
368         ...    response=${response_text}
369         ...    folder=${folder}
370         ...    base_name=response
371         ...    mapping=${mapping}
372         ...    iter_j_offset=${iter_j_offset}
373     END
374     RETURN    ${response_text}
375
376 Delete_Templated
377     [Documentation]    Resolve URI from folder, issue DELETE request.
378     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${location}=location
379     ${uri} =    Resolve_Text_From_Template_Folder
380     ...    folder=${folder}
381     ...    base_name=${location}
382     ...    extension=uri
383     ...    mapping=${mapping}
384     ...    percent_encode=True
385     ${response_text} =    Delete_From_Uri
386     ...    uri=${uri}
387     ...    session=${session}
388     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
389     ...    http_timeout=${http_timeout}
390     RETURN    ${response_text}
391
392 Verify_Response_As_Json_Templated
393     [Documentation]    Resolve expected JSON data, should be equal to provided \${response}.
394     ...    JSON normalization is used, endlines enabled for readability.
395     [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}=&{EMPTY}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
396     Verify_Response_Templated
397     ...    response=${response}
398     ...    folder=${folder}
399     ...    base_name=${base_name}
400     ...    extension=json
401     ...    mapping=${mapping}
402     ...    normalize_json=True
403     ...    endline=${\n}
404     ...    iterations=${iterations}
405     ...    iter_start=${iter_start}
406     ...    iter_j_offset=${iter_j_offset}
407
408 Verify_Response_As_Xml_Templated
409     [Documentation]    Resolve expected XML data, should be equal to provided \${response}.
410     ...    Endline set to empty, as this Resource does not support indented XML comparison.
411     [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}=&{EMPTY}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
412     Verify_Response_Templated
413     ...    response=${response}
414     ...    folder=${folder}
415     ...    base_name=${base_name}
416     ...    extension=xml
417     ...    mapping=${mapping}
418     ...    normalize_json=False
419     ...    endline=${EMPTY}
420     ...    iterations=${iterations}
421     ...    iter_start=${iter_start}
422     ...    iter_j_offset=${iter_j_offset}
423
424 Get_As_Json_From_Uri
425     [Documentation]    Specify JSON headers and return Get_From_Uri normalized response text.
426     [Arguments]    ${uri}    ${session}=default    ${http_timeout}=${EMPTY}    ${log_response}=True
427     ${response_text} =    Get_From_Uri
428     ...    uri=${uri}
429     ...    accept=${ACCEPT_EMPTY}
430     ...    session=${session}
431     ...    normalize_json=True
432     ...    http_timeout=${http_timeout}
433     ...    log_response=${log_response}
434     RETURN    ${response_text}
435
436 Get_As_Xml_From_Uri
437     [Documentation]    Specify XML headers and return Get_From_Uri response text.
438     [Arguments]    ${uri}    ${session}=default    ${http_timeout}=${EMPTY}    ${log_response}=True
439     ${response_text} =    Get_From_Uri
440     ...    uri=${uri}
441     ...    accept=${ACCEPT_XML}
442     ...    session=${session}
443     ...    normalize_json=False
444     ...    http_timeout=${http_timeout}
445     ...    log_response=${log_response}
446     RETURN    ${response_text}
447
448 Put_As_Json_To_Uri
449     [Documentation]    Specify JSON headers and return Put_To_Uri normalized response text.
450     ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
451     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
452     ${response_text} =    Put_To_Uri
453     ...    uri=${uri}
454     ...    data=${data}
455     ...    accept=${ACCEPT_EMPTY}
456     ...    content_type=${HEADERS_YANG_JSON}
457     ...    session=${session}
458     ...    normalize_json=True
459     ...    http_timeout=${http_timeout}
460     RETURN    ${response_text}
461
462 Put_As_Xml_To_Uri
463     [Documentation]    Specify XML headers and return Put_To_Uri response text.
464     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
465     ${response_text} =    Put_To_Uri
466     ...    uri=${uri}
467     ...    data=${data}
468     ...    accept=${ACCEPT_XML}
469     ...    content_type=${HEADERS_XML}
470     ...    session=${session}
471     ...    normalize_json=False
472     ...    http_timeout=${http_timeout}
473     RETURN    ${response_text}
474
475 Post_As_Json_To_Uri
476     [Documentation]    Specify JSON headers and return Post_To_Uri normalized response text.
477     ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
478     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
479     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
480     [Arguments]    ${uri}    ${data}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
481     ${response_text} =    Post_To_Uri
482     ...    uri=${uri}
483     ...    data=${data}
484     ...    accept=${ACCEPT_EMPTY}
485     ...    content_type=${HEADERS_YANG_JSON}
486     ...    session=${session}
487     ...    normalize_json=True
488     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
489     ...    explicit_status_codes=${explicit_status_codes}
490     ...    http_timeout=${http_timeout}
491     RETURN    ${response_text}
492
493 Post_As_Xml_To_Uri
494     [Documentation]    Specify XML headers and return Post_To_Uri response text.
495     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
496     ${response_text} =    Post_To_Uri
497     ...    uri=${uri}
498     ...    data=${data}
499     ...    accept=${ACCEPT_XML}
500     ...    content_type=${HEADERS_XML}
501     ...    session=${session}
502     ...    normalize_json=False
503     ...    http_timeout=${http_timeout}
504     RETURN    ${response_text}
505
506 Delete_From_Uri
507     [Documentation]    DELETE resource at URI, check status_code and return response text..
508     [Arguments]    ${uri}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
509     BuiltIn.Log    ${uri}
510     IF    """${http_timeout}""" == """${EMPTY}"""
511         ${response} =    RequestsLibrary.Delete_On_Session    ${session}    ${uri}
512     ELSE
513         ${response} =    RequestsLibrary.Delete_On_Session    ${session}    ${uri}    timeout=${http_timeout}
514     END
515     Check_Status_Code    ${response}    additional_allowed_status_codes=${additional_allowed_status_codes}
516     RETURN    ${response.text}
517
518 Resolve_Jmes_Path
519     [Documentation]    Reads JMES path from file ${folder}${/}jmespath.expr if the file exists and
520     ...    returns the JMES path. Empty string is returned otherwise.
521     [Arguments]    ${folder}
522     ${read_jmes_file} =    BuiltIn.Run Keyword And Return Status
523     ...    OperatingSystem.File Should Exist
524     ...    ${folder}${/}jmespath.expr
525     IF    ${read_jmes_file} == ${true}
526         ${jmes_expression} =    OperatingSystem.Get_File    ${folder}${/}jmespath.expr
527     ELSE
528         ${jmes_expression} =    Set Variable    ${None}
529     END
530     ${expression} =    BuiltIn.Set Variable If    ${read_jmes_file} == ${true}    ${jmes_expression}    ${EMPTY}
531     RETURN    ${expression}
532
533 Resolve_Volatiles_Path
534     [Documentation]    Reads Volatiles List from file ${folder}${/}volatiles.list if the file exists and
535     ...    returns the Volatiles List. Empty string is returned otherwise.
536     [Arguments]    ${folder}
537     ${read_volatiles_file} =    BuiltIn.Run Keyword And Return Status
538     ...    OperatingSystem.File Should Exist
539     ...    ${folder}${/}volatiles.list
540     IF    ${read_volatiles_file} == ${false}    RETURN    ${EMPTY}
541     ${volatiles} =    OperatingSystem.Get_File    ${folder}${/}volatiles.list
542     ${volatiles_list} =    String.Split_String    ${volatiles}    ${\n}
543     RETURN    ${volatiles_list}
544
545 Get_Templated
546     [Documentation]    Resolve URI from folder, call Get_From_Uri, return response text.
547     [Arguments]    ${folder}    ${accept}    ${mapping}=&{EMPTY}    ${session}=default    ${normalize_json}=False    ${http_timeout}=${EMPTY}    ${log_response}=True
548     ${uri} =    Resolve_Text_From_Template_Folder
549     ...    folder=${folder}
550     ...    base_name=location
551     ...    extension=uri
552     ...    mapping=${mapping}
553     ...    percent_encode=True
554     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
555     ${volatiles_list} =    Resolve_Volatiles_Path    ${folder}
556     ${response_text} =    Get_From_Uri
557     ...    uri=${uri}
558     ...    accept=${accept}
559     ...    session=${session}
560     ...    normalize_json=${normalize_json}
561     ...    jmes_path=${jmes_expression}
562     ...    http_timeout=${http_timeout}
563     ...    keys_with_volatiles=${volatiles_list}
564     ...    log_response=${log_response}
565     RETURN    ${response_text}
566
567 Put_Templated
568     [Documentation]    Resolve URI and data from folder, call Put_To_Uri, return response text.
569     [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}=&{EMPTY}
570     ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
571     ${uri} =    Resolve_Text_From_Template_Folder
572     ...    folder=${folder}
573     ...    base_name=location
574     ...    extension=uri
575     ...    mapping=${mapping}
576     ...    percent_encode=True
577     ${data} =    Resolve_Text_From_Template_Folder
578     ...    folder=${folder}
579     ...    base_name=${base_name}
580     ...    extension=${extension}
581     ...    mapping=${mapping}
582     ...    endline=${endline}
583     ...    iterations=${iterations}
584     ...    iter_start=${iter_start}
585     ...    iter_j_offset=${iter_j_offset}
586     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
587     ${response_text} =    Put_To_Uri
588     ...    uri=${uri}
589     ...    data=${data}
590     ...    content_type=${content_type}
591     ...    accept=${accept}
592     ...    session=${session}
593     ...    http_timeout=${http_timeout}
594     ...    normalize_json=${normalize_json}
595     ...    jmes_path=${jmes_expression}
596     RETURN    ${response_text}
597
598 Post_Templated
599     [Documentation]    Resolve URI and data from folder, call Post_To_Uri, return response text.
600     [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}=&{EMPTY}
601     ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${additional_allowed_status_codes}=${NO_STATUS_CODES}
602     ...    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
603     ${uri} =    Resolve_Text_From_Template_Folder
604     ...    folder=${folder}
605     ...    base_name=location
606     ...    extension=uri
607     ...    mapping=${mapping}
608     ...    percent_encode=True
609     ${data} =    Resolve_Text_From_Template_Folder
610     ...    folder=${folder}
611     ...    name_prefix=post_
612     ...    base_name=${base_name}
613     ...    extension=${extension}
614     ...    mapping=${mapping}
615     ...    endline=${endline}
616     ...    iterations=${iterations}
617     ...    iter_start=${iter_start}
618     ...    iter_j_offset=${iter_j_offset}
619     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
620     ${response_text} =    Post_To_Uri
621     ...    uri=${uri}
622     ...    data=${data}
623     ...    content_type=${content_type}
624     ...    accept=${accept}
625     ...    session=${session}
626     ...    jmes_path=${jmes_expression}
627     ...    normalize_json=${normalize_json}
628     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
629     ...    explicit_status_codes=${explicit_status_codes}
630     ...    http_timeout=${http_timeout}
631     RETURN    ${response_text}
632
633 Verify_Response_Templated
634     [Documentation]    Resolve expected text from template, provided response shuld be equal.
635     ...    If \${normalize_json}, perform normalization before comparison.
636     [Arguments]    ${response}    ${folder}    ${base_name}    ${extension}    ${mapping}=&{EMPTY}    ${normalize_json}=False
637     ...    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
638     # TODO: Support for XML-aware comparison could be added, but there are issues with namespaces and similar.
639     ${expected_text} =    Resolve_Text_From_Template_Folder
640     ...    folder=${folder}
641     ...    base_name=${base_name}
642     ...    extension=${extension}
643     ...    mapping=${mapping}
644     ...    endline=${endline}
645     ...    iterations=${iterations}
646     ...    iter_start=${iter_start}
647     ...    iter_j_offset=${iter_j_offset}
648     BuiltIn.Run_Keyword_And_Return_If
649     ...    """${expected_text}""" == """${EMPTY}"""
650     ...    BuiltIn.Should_Be_Equal
651     ...    ${EMPTY}
652     ...    ${response}
653     IF    ${normalize_json}
654         Normalize_Jsons_And_Compare    expected_raw=${expected_text}    actual_raw=${response}
655     ELSE
656         BuiltIn.Should_Be_Equal    ${expected_text}    ${response}
657     END
658
659 Get_From_Uri
660     [Documentation]    GET data from given URI, check status code and return response text.
661     ...    \${accept} is a Python object with headers to use.
662     ...    If \${normalize_json}, normalize as JSON text before returning.
663     [Arguments]    ${uri}    ${accept}=${ACCEPT_EMPTY}    ${session}=default    ${normalize_json}=False    ${jmes_path}=${EMPTY}    ${http_timeout}=${EMPTY}
664     ...    ${keys_with_volatiles}=${EMPTY}    ${log_response}=True
665     BuiltIn.Log    ${uri}
666     BuiltIn.Log    ${accept}
667     IF    """${http_timeout}""" == """${EMPTY}"""
668         ${response} =    RequestsLibrary.Get_On_Session    ${session}    url=${uri}    headers=${accept}
669     ELSE
670         ${response} =    RequestsLibrary.Get_On_Session
671         ...    ${session}
672         ...    url=${uri}
673         ...    headers=${accept}
674         ...    timeout=${http_timeout}
675     END
676     Check_Status_Code    ${response}    log_response=${log_response}
677     IF    not ${normalize_json}    RETURN    ${response.text}
678     ${text_normalized} =    norm_json.normalize_json_text
679     ...    ${response.text}
680     ...    jmes_path=${jmes_path}
681     ...    keys_with_volatiles=${keys_with_volatiles}
682     RETURN    ${text_normalized}
683
684 Put_To_Uri
685     [Documentation]    PUT data to given URI, check status code and return response text.
686     ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
687     ...    If \${normalize_json}, normalize text before returning.
688     [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
689     ...    ${jmes_path}=${EMPTY}    ${http_timeout}=${EMPTY}
690     BuiltIn.Log    ${uri}
691     BuiltIn.Log    ${data}
692     BuiltIn.Log    ${content_type}
693     BuiltIn.Log    ${accept}
694     ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
695     IF    """${http_timeout}""" == """${EMPTY}"""
696         ${response} =    RequestsLibrary.Put_On_Session
697         ...    ${session}
698         ...    ${uri}
699         ...    data=${data}
700         ...    headers=${headers}
701     ELSE
702         ${response} =    RequestsLibrary.Put_On_Session
703         ...    ${session}
704         ...    ${uri}
705         ...    data=${data}
706         ...    headers=${headers}
707         ...    timeout=${http_timeout}
708     END
709     Check_Status_Code    ${response}
710     IF    not ${normalize_json}    RETURN    ${response.text}
711     ${text_normalized} =    norm_json.normalize_json_text    ${response.text}    jmes_path=${jmes_path}
712     RETURN    ${text_normalized}
713
714 Post_To_Uri
715     [Documentation]    POST data to given URI, check status code and return response text.
716     ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
717     ...    If \${normalize_json}, normalize text before returning.
718     [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
719     ...    ${jmes_path}=${EMPTY}    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
720     BuiltIn.Log    ${uri}
721     BuiltIn.Log    ${data}
722     BuiltIn.Log    ${content_type}
723     BuiltIn.Log    ${accept}
724     ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
725     IF    """${http_timeout}""" == """${EMPTY}"""
726         ${response} =    RequestsLibrary.Post_On_Session
727         ...    ${session}
728         ...    ${uri}
729         ...    data=${data}
730         ...    headers=${headers}
731     ELSE
732         ${response} =    RequestsLibrary.Post_On_Session
733         ...    ${session}
734         ...    ${uri}
735         ...    data=${data}
736         ...    headers=${headers}
737         ...    timeout=${http_timeout}
738     END
739     Check_Status_Code
740     ...    ${response}
741     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
742     ...    explicit_status_codes=${explicit_status_codes}
743     IF    not ${normalize_json}    RETURN    ${response.text}
744     ${text_normalized} =    norm_json.normalize_json_text    ${response.text}    jmes_path=${jmes_path}
745     RETURN    ${text_normalized}
746
747 Check_Status_Code
748     [Documentation]    Log response text, check status_code is one of allowed ones. In cases where this keyword is
749     ...    called in a WUKS it could end up logging tons of data and it may be desired to skip the logging by passing
750     ...    log_response=False, but by default it remains True.
751     [Arguments]    ${response}    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${log_response}=True
752     # TODO: Remove overlap with keywords from Utils.robot
753     IF    "${log_response}" == "True"    BuiltIn.Log    ${response.text}
754     IF    "${log_response}" == "True"    BuiltIn.Log    ${response.status_code}
755     # In order to allow other existing keywords to consume this keyword by passing a single non-list status code, we need to
756     # check the type of the argument passed and convert those single non-list codes in to a one item list
757     ${status_codes_type} =    Evaluate    type($additional_allowed_status_codes).__name__
758     IF    "${status_codes_type}"!="list"
759         ${allowed_status_codes_list} =    Create List    ${additional_allowed_status_codes}
760     ELSE
761         ${allowed_status_codes_list} =    Set Variable    ${additional_allowed_status_codes}
762     END
763     ${status_codes_type} =    Evaluate    type($explicit_status_codes).__name__
764     IF    "${status_codes_type}"!="list"
765         ${explicit_status_codes_list} =    Create List    ${explicit_status_codes}
766     ELSE
767         ${explicit_status_codes_list} =    Set Variable    ${explicit_status_codes}
768     END
769     BuiltIn.Run_Keyword_And_Return_If
770     ...    """${explicit_status_codes_list}""" != """${NO_STATUS_CODES}"""
771     ...    Collections.List_Should_Contain_Value
772     ...    ${explicit_status_codes_list}
773     ...    ${response.status_code}
774     ${final_allowd_list} =    Collections.Combine_Lists    ${ALLOWED_STATUS_CODES}    ${allowed_status_codes_list}
775     Collections.List_Should_Contain_Value    ${final_allowd_list}    ${response.status_code}
776
777 Join_Two_Headers
778     [Documentation]    Take two dicts, join them, return result. Second argument values take precedence.
779     [Arguments]    ${first}    ${second}
780     ${accumulator} =    Collections.Copy_Dictionary    ${first}
781     ${items_to_add} =    Collections.Get_Dictionary_Items    ${second}
782     Collections.Set_To_Dictionary    ${accumulator}    @{items_to_add}
783     BuiltIn.Log    ${accumulator}
784     RETURN    ${accumulator}
785
786 Resolve_Text_From_Template_Folder
787     [Documentation]    Read a template from folder, strip endline, make changes according to mapping, return the result.
788     ...    If \${iterations} value is present, put text together from "prolog", "item" and "epilog" parts,
789     ...    where additional template variable ${i} goes from ${iter_start}, by one ${iterations} times.
790     ...    Template variable ${j} is calculated as ${i} incremented by offset ${iter_j_offset} ( j = i + iter_j_offset )
791     ...    used to create non uniform data in order to be able to validate UPDATE operations.
792     ...    POST (as opposed to PUT) needs slightly different data, \${name_prefix} may be used to distinguish.
793     ...    (Actually, it is GET who formats data differently when URI is a top-level container.)
794     [Arguments]    ${folder}    ${name_prefix}=${EMPTY}    ${base_name}=data    ${extension}=json    ${mapping}=${EMPTY}    ${iterations}=${EMPTY}
795     ...    ${iter_start}=1    ${iter_j_offset}=0    ${endline}=${\n}    ${percent_encode}=False
796     BuiltIn.Run_Keyword_And_Return_If
797     ...    not "${iterations}"
798     ...    Resolve_Text_From_Template_File
799     ...    folder=${folder}
800     ...    file_name=${name_prefix}${base_name}.${extension}
801     ...    mapping=${mapping}
802     ...    percent_encode=${percent_encode}
803     ${prolog} =    Resolve_Text_From_Template_File
804     ...    folder=${folder}
805     ...    file_name=${name_prefix}${base_name}.prolog.${extension}
806     ...    mapping=${mapping}
807     ...    percent_encode=${percent_encode}
808     ${epilog} =    Resolve_Text_From_Template_File
809     ...    folder=${folder}
810     ...    file_name=${name_prefix}${base_name}.epilog.${extension}
811     ...    mapping=${mapping}
812     ...    percent_encode=${percent_encode}
813     # Even POST uses the same item template (except indentation), so name prefix is ignored.
814     ${item_template} =    Resolve_Text_From_Template_File
815     ...    folder=${folder}
816     ...    file_name=${base_name}.item.${extension}
817     ...    mapping=${mapping}
818     ${items} =    BuiltIn.Create_List
819     ${separator} =    BuiltIn.Set_Variable_If    '${extension}' != 'json'    ${endline}    ,${endline}
820     FOR    ${iteration}    IN RANGE    ${iter_start}    ${iterations}+${iter_start}
821         IF    ${iteration} > ${iter_start}
822             Collections.Append_To_List    ${items}    ${separator}
823         END
824         ${j} =    BuiltIn.Evaluate    ${iteration}+${iter_j_offset}
825         ${item} =    BuiltIn.Evaluate
826         ...    string.Template('''${item_template}''').substitute({"i":"${iteration}", "j":${j}})
827         ...    modules=string
828         Collections.Append_To_List    ${items}    ${item}
829         # TODO: The following makes ugly result for iterations=0. Should we fix that?
830     END
831     ${final_text} =    BuiltIn.Catenate    SEPARATOR=    ${prolog}    ${endline}    @{items}    ${endline}
832     ...    ${epilog}
833     RETURN    ${final_text}
834
835 Resolve_Text_From_Template_File
836     [Documentation]    Check if ${folder}.${ODL_STREAM}/${file_name} exists. If yes read and Log contents of file ${folder}.${ODL_STREAM}/${file_name},
837     ...    remove endline, perform safe substitution, return result.
838     ...    If no do it with the default ${folder}/${file_name}.
839     [Arguments]    ${folder}    ${file_name}    ${mapping}=&{EMPTY}    ${percent_encode}=False
840     ${file_path_stream} =    BuiltIn.Set Variable    ${folder}.${ODL_STREAM}${/}${file_name}
841     ${file_stream_exists} =    BuiltIn.Run Keyword And Return Status
842     ...    OperatingSystem.File Should Exist
843     ...    ${file_path_stream}
844     ${file_path} =    BuiltIn.Set Variable If
845     ...    ${file_stream_exists}
846     ...    ${file_path_stream}
847     ...    ${folder}${/}${file_name}
848     ${template} =    OperatingSystem.Get_File    ${file_path}
849     BuiltIn.Log    ${template}
850     IF    ${percent_encode} == True
851         ${mapping_to_use} =    Encode_Mapping    ${mapping}
852     ELSE
853         ${mapping_to_use} =    BuiltIn.Set_Variable    ${mapping}
854     END
855     ${final_text} =    BuiltIn.Evaluate
856     ...    string.Template('''${template}'''.rstrip()).safe_substitute(${mapping_to_use})
857     ...    modules=string
858     RETURN    ${final_text}
859
860     # Final text is logged where used.
861
862 Normalize_Jsons_And_Compare
863     [Documentation]    Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
864     [Arguments]    ${expected_raw}    ${actual_raw}
865     ${expected_normalized} =    norm_json.normalize_json_text    ${expected_raw}
866     ${actual_normalized} =    norm_json.normalize_json_text    ${actual_raw}
867     # Should_Be_Equal shall print nice diff-style line comparison.
868     BuiltIn.Should_Be_Equal    ${expected_normalized}    ${actual_normalized}
869     # TODO: Add garbage collection? Check whether the temporary data accumulates.
870
871 Normalize_Jsons_With_Bits_And_Compare
872     [Documentation]    Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
873     [Arguments]    ${expected_raw}    ${actual_raw}    ${keys_with_bits}=${KEYS_WITH_BITS}
874     ${expected_normalized} =    norm_json.normalize_json_text    ${expected_raw}    keys_with_bits=${keys_with_bits}
875     ${actual_normalized} =    norm_json.normalize_json_text    ${actual_raw}    keys_with_bits=${keys_with_bits}
876     BuiltIn.Should_Be_Equal    ${expected_normalized}    ${actual_normalized}
877
878 Encode_Mapping
879     [Arguments]    ${mapping}
880     BuiltIn.Log    mapping: ${mapping}
881     ${encoded_mapping} =    BuiltIn.Create_Dictionary
882     FOR    ${key}    ${value}    IN    &{mapping}
883         ${encoded_value} =    Percent_Encode_String    ${value}
884         Collections.Set_To_Dictionary    ${encoded_mapping}    ${key}    ${encoded_value}
885     END
886     RETURN    ${encoded_mapping}
887
888 Percent_Encode_String
889     [Documentation]    Percent encodes reserved characters in the given string so it can be used as part of url.
890     [Arguments]    ${value}
891     ${encoded} =    String.Replace_String_Using_Regexp    ${value}    :    %3A
892     RETURN    ${encoded}