Fix Get On Session errors
[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 # List of integers, not strings. Used by both PUT and DELETE (if the resource should have been present).
129 @{ALLOWED_STATUS_CODES}
130 ...                                 ${200}
131 ...                                 ${201}
132 ...                                 ${204}
133 @{DATA_VALIDATION_ERROR}            ${400}    # For testing mildly negative scenarios where ODL reports user error.
134 # List of integers, not strings. Used by DELETE if the resource may be not present.
135 @{DELETED_STATUS_CODES}
136 ...                                 ${404}
137 ...                                 ${409}
138 # TODO: Add option for delete to require 404.
139 @{INTERNAL_SERVER_ERROR}            ${500}    # Only for testing severely negative scenarios where ODL cannot recover.
140 @{KEYS_WITH_BITS}                   op    # the default list with keys to be sorted when norm_json libray is used
141 @{NO_STATUS_CODES}
142 # List of integers, not strings. Used in Keystone Authentication when the user is not authorized to use the requested resource.
143 @{UNAUTHORIZED_STATUS_CODES}
144 ...                                 ${401}
145
146
147 *** Keywords ***
148 Create_Default_Session
149     [Documentation]    Create "default" session to ${url} with authentication and connection parameters.
150     ...    This Keyword is in this Resource only so that user do not need to call RequestsLibrary directly.
151     [Arguments]    ${url}=http://${ODL_SYSTEM_IP}:${RESTCONFPORT}    ${auth}=${AUTH}    ${timeout}=${DEFAULT_TIMEOUT_HTTP}    ${max_retries}=0
152     RequestsLibrary.Create_Session
153     ...    alias=default
154     ...    url=${url}
155     ...    auth=${auth}
156     ...    timeout=${timeout}
157     ...    max_retries=${max_retries}
158
159 Get_As_Json_Templated
160     [Documentation]    Add arguments sensible for JSON data, return Get_Templated response text.
161     ...    Optionally, verification against JSON data (may be iterated) is called.
162     ...    Only subset of JSON data is verified and returned if JMES path is specified in
163     ...    file ${folder}${/}jmespath.expr.
164     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
165     ...    ${http_timeout}=${EMPTY}    ${log_response}=True    ${iter_j_offset}=0
166     ${response_text} =    Get_Templated
167     ...    folder=${folder}
168     ...    mapping=${mapping}
169     ...    accept=${ACCEPT_EMPTY}
170     ...    session=${session}
171     ...    normalize_json=True
172     ...    http_timeout=${http_timeout}
173     ...    log_response=${log_response}
174     IF    ${verify}
175         Verify_Response_As_Json_Templated
176         ...    response=${response_text}
177         ...    folder=${folder}
178         ...    base_name=data
179         ...    mapping=${mapping}
180         ...    iterations=${iterations}
181         ...    iter_start=${iter_start}
182         ...    iter_j_offset=${iter_j_offset}
183     END
184     RETURN    ${response_text}
185
186 Get_As_Xml_Templated
187     [Documentation]    Add arguments sensible for XML data, return Get_Templated response text.
188     ...    Optionally, verification against XML data (may be iterated) is called.
189     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
190     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
191     ${response_text} =    Get_Templated
192     ...    folder=${folder}
193     ...    mapping=${mapping}
194     ...    accept=${ACCEPT_XML}
195     ...    session=${session}
196     ...    normalize_json=False
197     ...    http_timeout=${http_timeout}
198     IF    ${verify}
199         Verify_Response_As_Xml_Templated
200         ...    response=${response_text}
201         ...    folder=${folder}
202         ...    base_name=data
203         ...    mapping=${mapping}
204         ...    iterations=${iterations}
205         ...    iter_start=${iter_start}
206         ...    iter_j_offset=${iter_j_offset}
207     END
208     RETURN    ${response_text}
209
210 Put_As_Json_Templated
211     [Documentation]    Add arguments sensible for JSON data, return Put_Templated response text.
212     ...    Optionally, verification against response.json (no iteration) is called.
213     ...    Only subset of JSON data is verified and returned if JMES path is specified in
214     ...    file ${folder}${/}jmespath.expr.
215     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
216     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
217     ${response_text} =    Put_Templated
218     ...    folder=${folder}
219     ...    base_name=data
220     ...    extension=json
221     ...    accept=${ACCEPT_EMPTY}
222     ...    content_type=${HEADERS_YANG_JSON}
223     ...    mapping=${mapping}
224     ...    session=${session}
225     ...    normalize_json=True
226     ...    endline=${\n}
227     ...    iterations=${iterations}
228     ...    iter_start=${iter_start}
229     ...    http_timeout=${http_timeout}
230     ...    iter_j_offset=${iter_j_offset}
231     IF    ${verify}
232         Verify_Response_As_Json_Templated
233         ...    response=${response_text}
234         ...    folder=${folder}
235         ...    base_name=response
236         ...    mapping=${mapping}
237         ...    iter_j_offset=${iter_j_offset}
238     END
239     RETURN    ${response_text}
240
241 Put_As_Xml_Templated
242     [Documentation]    Add arguments sensible for XML data, return Put_Templated response text.
243     ...    Optionally, verification against response.xml (no iteration) is called.
244     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
245     ...    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
246     # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
247     ${response_text} =    Put_Templated
248     ...    folder=${folder}
249     ...    base_name=data
250     ...    extension=xml
251     ...    accept=${ACCEPT_XML}
252     ...    content_type=${HEADERS_XML}
253     ...    mapping=${mapping}
254     ...    session=${session}
255     ...    normalize_json=False
256     ...    endline=${\n}
257     ...    iterations=${iterations}
258     ...    iter_start=${iter_start}
259     ...    http_timeout=${http_timeout}
260     ...    iter_j_offset=${iter_j_offset}
261     IF    ${verify}
262         Verify_Response_As_Xml_Templated
263         ...    response=${response_text}
264         ...    folder=${folder}
265         ...    base_name=response
266         ...    mapping=${mapping}
267         ...    iter_j_offset=${iter_j_offset}
268     END
269     RETURN    ${response_text}
270
271 Post_As_Json_Templated
272     [Documentation]    Add arguments sensible for JSON data, return Post_Templated response text.
273     ...    Optionally, verification against response.json (no iteration) is called.
274     ...    Only subset of JSON data is verified and returned if JMES path is specified in
275     ...    file ${folder}${/}jmespath.expr.
276     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
277     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
278     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
279     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
280     ${response_text} =    Post_Templated
281     ...    folder=${folder}
282     ...    base_name=data
283     ...    extension=json
284     ...    accept=${ACCEPT_EMPTY}
285     ...    content_type=${HEADERS_YANG_JSON}
286     ...    mapping=${mapping}
287     ...    session=${session}
288     ...    normalize_json=True
289     ...    endline=${\n}
290     ...    iterations=${iterations}
291     ...    iter_start=${iter_start}
292     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
293     ...    explicit_status_codes=${explicit_status_codes}
294     ...    http_timeout=${http_timeout}
295     ...    iter_j_offset=${iter_j_offset}
296     IF    ${verify}
297         Verify_Response_As_Json_Templated
298         ...    response=${response_text}
299         ...    folder=${folder}
300         ...    base_name=response
301         ...    mapping=${mapping}
302         ...    iter_j_offset=${iter_j_offset}
303     END
304     RETURN    ${response_text}
305
306 Post_As_Json_Rfc8040_Templated
307     [Documentation]    Add arguments sensible for JSON data, return Post_Templated response text.
308     ...    Optionally, verification against response.json (no iteration) is called.
309     ...    Only subset of JSON data is verified and returned if JMES path is specified in
310     ...    file ${folder}${/}jmespath.expr.
311     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
312     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
313     ...    RFC8040 defines RESTCONF protocol, for configuring data defined in YANG version 1
314     ...    or YANG version 1.1, using the datastore concepts defined in NETCONF.
315     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
316     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
317     ${response_text} =    Post_Templated
318     ...    folder=${folder}
319     ...    base_name=data
320     ...    extension=json
321     ...    accept=${ACCEPT_EMPTY}
322     ...    content_type=${HEADERS_YANG_RFC8040_JSON}
323     ...    mapping=${mapping}
324     ...    session=${session}
325     ...    normalize_json=True
326     ...    endline=${\n}
327     ...    iterations=${iterations}
328     ...    iter_start=${iter_start}
329     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
330     ...    explicit_status_codes=${explicit_status_codes}
331     ...    http_timeout=${http_timeout}
332     ...    iter_j_offset=${iter_j_offset}
333     IF    ${verify}
334         Verify_Response_As_Json_Templated
335         ...    response=${response_text}
336         ...    folder=${folder}
337         ...    base_name=response
338         ...    mapping=${mapping}
339         ...    iter_j_offset=${iter_j_offset}
340     END
341     RETURN    ${response_text}
342
343 Post_As_Xml_Templated
344     [Documentation]    Add arguments sensible for XML data, return Post_Templated response text.
345     ...    Optionally, verification against response.xml (no iteration) is called.
346     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${verify}=False    ${iterations}=${EMPTY}    ${iter_start}=1
347     ...    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
348     # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
349     ${response_text} =    Post_Templated
350     ...    folder=${folder}
351     ...    base_name=data
352     ...    extension=xml
353     ...    accept=${ACCEPT_XML}
354     ...    content_type=${HEADERS_XML}
355     ...    mapping=${mapping}
356     ...    session=${session}
357     ...    normalize_json=False
358     ...    endline=${\n}
359     ...    iterations=${iterations}
360     ...    iter_start=${iter_start}
361     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
362     ...    explicit_status_codes=${explicit_status_codes}
363     ...    http_timeout=${http_timeout}
364     ...    iter_j_offset=${iter_j_offset}
365     IF    ${verify}
366         Verify_Response_As_Xml_Templated
367         ...    response=${response_text}
368         ...    folder=${folder}
369         ...    base_name=response
370         ...    mapping=${mapping}
371         ...    iter_j_offset=${iter_j_offset}
372     END
373     RETURN    ${response_text}
374
375 Delete_Templated
376     [Documentation]    Resolve URI from folder, issue DELETE request.
377     [Arguments]    ${folder}    ${mapping}=&{EMPTY}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${location}=location
378     ${uri} =    Resolve_Text_From_Template_Folder
379     ...    folder=${folder}
380     ...    base_name=${location}
381     ...    extension=uri
382     ...    mapping=${mapping}
383     ...    percent_encode=True
384     ${response_text} =    Delete_From_Uri
385     ...    uri=${uri}
386     ...    session=${session}
387     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
388     ...    http_timeout=${http_timeout}
389     RETURN    ${response_text}
390
391 Verify_Response_As_Json_Templated
392     [Documentation]    Resolve expected JSON data, should be equal to provided \${response}.
393     ...    JSON normalization is used, endlines enabled for readability.
394     [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}=&{EMPTY}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
395     Verify_Response_Templated
396     ...    response=${response}
397     ...    folder=${folder}
398     ...    base_name=${base_name}
399     ...    extension=json
400     ...    mapping=${mapping}
401     ...    normalize_json=True
402     ...    endline=${\n}
403     ...    iterations=${iterations}
404     ...    iter_start=${iter_start}
405     ...    iter_j_offset=${iter_j_offset}
406
407 Verify_Response_As_Xml_Templated
408     [Documentation]    Resolve expected XML data, should be equal to provided \${response}.
409     ...    Endline set to empty, as this Resource does not support indented XML comparison.
410     [Arguments]    ${response}    ${folder}    ${base_name}=response    ${mapping}=&{EMPTY}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
411     Verify_Response_Templated
412     ...    response=${response}
413     ...    folder=${folder}
414     ...    base_name=${base_name}
415     ...    extension=xml
416     ...    mapping=${mapping}
417     ...    normalize_json=False
418     ...    endline=${EMPTY}
419     ...    iterations=${iterations}
420     ...    iter_start=${iter_start}
421     ...    iter_j_offset=${iter_j_offset}
422
423 Get_As_Json_From_Uri
424     [Documentation]    Specify JSON headers and return Get_From_Uri normalized response text.
425     [Arguments]    ${uri}    ${session}=default    ${http_timeout}=${EMPTY}    ${log_response}=True
426     ${response_text} =    Get_From_Uri
427     ...    uri=${uri}
428     ...    accept=${ACCEPT_EMPTY}
429     ...    session=${session}
430     ...    normalize_json=True
431     ...    http_timeout=${http_timeout}
432     ...    log_response=${log_response}
433     RETURN    ${response_text}
434
435 Get_As_Xml_From_Uri
436     [Documentation]    Specify XML headers and return Get_From_Uri response text.
437     [Arguments]    ${uri}    ${session}=default    ${http_timeout}=${EMPTY}    ${log_response}=True
438     ${response_text} =    Get_From_Uri
439     ...    uri=${uri}
440     ...    accept=${ACCEPT_XML}
441     ...    session=${session}
442     ...    normalize_json=False
443     ...    http_timeout=${http_timeout}
444     ...    log_response=${log_response}
445     RETURN    ${response_text}
446
447 Put_As_Json_To_Uri
448     [Documentation]    Specify JSON headers and return Put_To_Uri normalized response text.
449     ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
450     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
451     ${response_text} =    Put_To_Uri
452     ...    uri=${uri}
453     ...    data=${data}
454     ...    accept=${ACCEPT_EMPTY}
455     ...    content_type=${HEADERS_YANG_JSON}
456     ...    session=${session}
457     ...    normalize_json=True
458     ...    http_timeout=${http_timeout}
459     RETURN    ${response_text}
460
461 Put_As_Xml_To_Uri
462     [Documentation]    Specify XML headers and return Put_To_Uri response text.
463     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
464     ${response_text} =    Put_To_Uri
465     ...    uri=${uri}
466     ...    data=${data}
467     ...    accept=${ACCEPT_XML}
468     ...    content_type=${HEADERS_XML}
469     ...    session=${session}
470     ...    normalize_json=False
471     ...    http_timeout=${http_timeout}
472     RETURN    ${response_text}
473
474 Post_As_Json_To_Uri
475     [Documentation]    Specify JSON headers and return Post_To_Uri normalized response text.
476     ...    Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
477     ...    Response status code must be one of values from ${explicit_status_codes} if specified or one of set
478     ...    created from all positive HTTP status codes together with ${additional_allowed_status_codes}.
479     [Arguments]    ${uri}    ${data}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
480     ${response_text} =    Post_To_Uri
481     ...    uri=${uri}
482     ...    data=${data}
483     ...    accept=${ACCEPT_EMPTY}
484     ...    content_type=${HEADERS_YANG_JSON}
485     ...    session=${session}
486     ...    normalize_json=True
487     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
488     ...    explicit_status_codes=${explicit_status_codes}
489     ...    http_timeout=${http_timeout}
490     RETURN    ${response_text}
491
492 Post_As_Xml_To_Uri
493     [Documentation]    Specify XML headers and return Post_To_Uri response text.
494     [Arguments]    ${uri}    ${data}    ${session}=default    ${http_timeout}=${EMPTY}
495     ${response_text} =    Post_To_Uri
496     ...    uri=${uri}
497     ...    data=${data}
498     ...    accept=${ACCEPT_XML}
499     ...    content_type=${HEADERS_XML}
500     ...    session=${session}
501     ...    normalize_json=False
502     ...    http_timeout=${http_timeout}
503     RETURN    ${response_text}
504
505 Delete_From_Uri
506     [Documentation]    DELETE resource at URI, check status_code and return response text..
507     [Arguments]    ${uri}    ${session}=default    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
508     BuiltIn.Log    ${uri}
509     IF    """${http_timeout}""" == """${EMPTY}"""
510         ${response} =    RequestsLibrary.Delete_On_Session    ${session}    ${uri}
511     ELSE
512         ${response} =    RequestsLibrary.Delete_On_Session    ${session}    ${uri}    timeout=${http_timeout}
513     END
514     Check_Status_Code    ${response}    additional_allowed_status_codes=${additional_allowed_status_codes}
515     RETURN    ${response.text}
516
517 Resolve_Jmes_Path
518     [Documentation]    Reads JMES path from file ${folder}${/}jmespath.expr if the file exists and
519     ...    returns the JMES path. Empty string is returned otherwise.
520     [Arguments]    ${folder}
521     ${read_jmes_file} =    BuiltIn.Run Keyword And Return Status
522     ...    OperatingSystem.File Should Exist
523     ...    ${folder}${/}jmespath.expr
524     IF    ${read_jmes_file} == ${true}
525         ${jmes_expression} =    OperatingSystem.Get_File    ${folder}${/}jmespath.expr
526     ELSE
527         ${jmes_expression} =    Set Variable    ${None}
528     END
529     ${expression} =    BuiltIn.Set Variable If    ${read_jmes_file} == ${true}    ${jmes_expression}    ${EMPTY}
530     RETURN    ${expression}
531
532 Resolve_Volatiles_Path
533     [Documentation]    Reads Volatiles List from file ${folder}${/}volatiles.list if the file exists and
534     ...    returns the Volatiles List. Empty string is returned otherwise.
535     [Arguments]    ${folder}
536     ${read_volatiles_file} =    BuiltIn.Run Keyword And Return Status
537     ...    OperatingSystem.File Should Exist
538     ...    ${folder}${/}volatiles.list
539     IF    ${read_volatiles_file} == ${false}    RETURN    ${EMPTY}
540     ${volatiles} =    OperatingSystem.Get_File    ${folder}${/}volatiles.list
541     ${volatiles_list} =    String.Split_String    ${volatiles}    ${\n}
542     RETURN    ${volatiles_list}
543
544 Get_Templated
545     [Documentation]    Resolve URI from folder, call Get_From_Uri, return response text.
546     [Arguments]    ${folder}    ${accept}    ${mapping}=&{EMPTY}    ${session}=default    ${normalize_json}=False    ${http_timeout}=${EMPTY}    ${log_response}=True
547     ${uri} =    Resolve_Text_From_Template_Folder
548     ...    folder=${folder}
549     ...    base_name=location
550     ...    extension=uri
551     ...    mapping=${mapping}
552     ...    percent_encode=True
553     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
554     ${volatiles_list} =    Resolve_Volatiles_Path    ${folder}
555     ${response_text} =    Get_From_Uri
556     ...    uri=${uri}
557     ...    accept=${accept}
558     ...    session=${session}
559     ...    normalize_json=${normalize_json}
560     ...    jmes_path=${jmes_expression}
561     ...    http_timeout=${http_timeout}
562     ...    keys_with_volatiles=${volatiles_list}
563     ...    log_response=${log_response}
564     RETURN    ${response_text}
565
566 Put_Templated
567     [Documentation]    Resolve URI and data from folder, call Put_To_Uri, return response text.
568     [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}=&{EMPTY}
569     ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
570     ${uri} =    Resolve_Text_From_Template_Folder
571     ...    folder=${folder}
572     ...    base_name=location
573     ...    extension=uri
574     ...    mapping=${mapping}
575     ...    percent_encode=True
576     ${data} =    Resolve_Text_From_Template_Folder
577     ...    folder=${folder}
578     ...    base_name=${base_name}
579     ...    extension=${extension}
580     ...    mapping=${mapping}
581     ...    endline=${endline}
582     ...    iterations=${iterations}
583     ...    iter_start=${iter_start}
584     ...    iter_j_offset=${iter_j_offset}
585     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
586     ${response_text} =    Put_To_Uri
587     ...    uri=${uri}
588     ...    data=${data}
589     ...    content_type=${content_type}
590     ...    accept=${accept}
591     ...    session=${session}
592     ...    http_timeout=${http_timeout}
593     ...    normalize_json=${normalize_json}
594     ...    jmes_path=${jmes_expression}
595     RETURN    ${response_text}
596
597 Post_Templated
598     [Documentation]    Resolve URI and data from folder, call Post_To_Uri, return response text.
599     [Arguments]    ${folder}    ${base_name}    ${extension}    ${content_type}    ${accept}    ${mapping}=&{EMPTY}
600     ...    ${session}=default    ${normalize_json}=False    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${additional_allowed_status_codes}=${NO_STATUS_CODES}
601     ...    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}    ${iter_j_offset}=0
602     ${uri} =    Resolve_Text_From_Template_Folder
603     ...    folder=${folder}
604     ...    base_name=location
605     ...    extension=uri
606     ...    mapping=${mapping}
607     ...    percent_encode=True
608     ${data} =    Resolve_Text_From_Template_Folder
609     ...    folder=${folder}
610     ...    name_prefix=post_
611     ...    base_name=${base_name}
612     ...    extension=${extension}
613     ...    mapping=${mapping}
614     ...    endline=${endline}
615     ...    iterations=${iterations}
616     ...    iter_start=${iter_start}
617     ...    iter_j_offset=${iter_j_offset}
618     ${jmes_expression} =    Resolve_Jmes_Path    ${folder}
619     ${response_text} =    Post_To_Uri
620     ...    uri=${uri}
621     ...    data=${data}
622     ...    content_type=${content_type}
623     ...    accept=${accept}
624     ...    session=${session}
625     ...    jmes_path=${jmes_expression}
626     ...    normalize_json=${normalize_json}
627     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
628     ...    explicit_status_codes=${explicit_status_codes}
629     ...    http_timeout=${http_timeout}
630     RETURN    ${response_text}
631
632 Verify_Response_Templated
633     [Documentation]    Resolve expected text from template, provided response shuld be equal.
634     ...    If \${normalize_json}, perform normalization before comparison.
635     [Arguments]    ${response}    ${folder}    ${base_name}    ${extension}    ${mapping}=&{EMPTY}    ${normalize_json}=False
636     ...    ${endline}=${\n}    ${iterations}=${EMPTY}    ${iter_start}=1    ${iter_j_offset}=0
637     # TODO: Support for XML-aware comparison could be added, but there are issues with namespaces and similar.
638     ${expected_text} =    Resolve_Text_From_Template_Folder
639     ...    folder=${folder}
640     ...    base_name=${base_name}
641     ...    extension=${extension}
642     ...    mapping=${mapping}
643     ...    endline=${endline}
644     ...    iterations=${iterations}
645     ...    iter_start=${iter_start}
646     ...    iter_j_offset=${iter_j_offset}
647     BuiltIn.Run_Keyword_And_Return_If
648     ...    """${expected_text}""" == """${EMPTY}"""
649     ...    BuiltIn.Should_Be_Equal
650     ...    ${EMPTY}
651     ...    ${response}
652     IF    ${normalize_json}
653         Normalize_Jsons_And_Compare    expected_raw=${expected_text}    actual_raw=${response}
654     ELSE
655         BuiltIn.Should_Be_Equal    ${expected_text}    ${response}
656     END
657
658 Get_From_Uri
659     [Documentation]    GET data from given URI, check status code and return response text.
660     ...    \${accept} is a Python object with headers to use.
661     ...    If \${normalize_json}, normalize as JSON text before returning.
662     [Arguments]    ${uri}    ${accept}=${ACCEPT_EMPTY}    ${session}=default    ${normalize_json}=False    ${jmes_path}=${EMPTY}    ${http_timeout}=${EMPTY}
663     ...    ${keys_with_volatiles}=${EMPTY}    ${log_response}=True
664     BuiltIn.Log    ${uri}
665     BuiltIn.Log    ${accept}
666     IF    """${http_timeout}""" == """${EMPTY}"""
667         ${response} =    RequestsLibrary.Get_On_Session    ${session}    url=${uri}    headers=${accept}
668     ELSE
669         ${response} =    RequestsLibrary.Get_On_Session
670         ...    ${session}
671         ...    url=${uri}
672         ...    headers=${accept}
673         ...    timeout=${http_timeout}
674     END
675     Check_Status_Code    ${response}    log_response=${log_response}
676     IF    not ${normalize_json}    RETURN    ${response.text}
677     ${text_normalized} =    norm_json.normalize_json_text
678     ...    ${response.text}
679     ...    jmes_path=${jmes_path}
680     ...    keys_with_volatiles=${keys_with_volatiles}
681     RETURN    ${text_normalized}
682
683 Put_To_Uri
684     [Documentation]    PUT data to given URI, check status code and return response text.
685     ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
686     ...    If \${normalize_json}, normalize text before returning.
687     [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
688     ...    ${jmes_path}=${EMPTY}    ${http_timeout}=${EMPTY}
689     BuiltIn.Log    ${uri}
690     BuiltIn.Log    ${data}
691     BuiltIn.Log    ${content_type}
692     BuiltIn.Log    ${accept}
693     ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
694     IF    """${http_timeout}""" == """${EMPTY}"""
695         ${response} =    RequestsLibrary.Put_On_Session
696         ...    ${session}
697         ...    ${uri}
698         ...    data=${data}
699         ...    headers=${headers}
700     ELSE
701         ${response} =    RequestsLibrary.Put_On_Session
702         ...    ${session}
703         ...    ${uri}
704         ...    data=${data}
705         ...    headers=${headers}
706         ...    timeout=${http_timeout}
707     END
708     Check_Status_Code    ${response}
709     IF    not ${normalize_json}    RETURN    ${response.text}
710     ${text_normalized} =    norm_json.normalize_json_text    ${response.text}    jmes_path=${jmes_path}
711     RETURN    ${text_normalized}
712
713 Post_To_Uri
714     [Documentation]    POST data to given URI, check status code and return response text.
715     ...    \${content_type} and \${accept} are mandatory Python objects with headers to use.
716     ...    If \${normalize_json}, normalize text before returning.
717     [Arguments]    ${uri}    ${data}    ${content_type}    ${accept}    ${session}=default    ${normalize_json}=False
718     ...    ${jmes_path}=${EMPTY}    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${http_timeout}=${EMPTY}
719     BuiltIn.Log    ${uri}
720     BuiltIn.Log    ${data}
721     BuiltIn.Log    ${content_type}
722     BuiltIn.Log    ${accept}
723     ${headers} =    Join_Two_Headers    first=${content_type}    second=${accept}
724     IF    """${http_timeout}""" == """${EMPTY}"""
725         ${response} =    RequestsLibrary.Post_On_Session
726         ...    ${session}
727         ...    ${uri}
728         ...    data=${data}
729         ...    headers=${headers}
730     ELSE
731         ${response} =    RequestsLibrary.Post_On_Session
732         ...    ${session}
733         ...    ${uri}
734         ...    data=${data}
735         ...    headers=${headers}
736         ...    timeout=${http_timeout}
737     END
738     Check_Status_Code
739     ...    ${response}
740     ...    additional_allowed_status_codes=${additional_allowed_status_codes}
741     ...    explicit_status_codes=${explicit_status_codes}
742     IF    not ${normalize_json}    RETURN    ${response.text}
743     ${text_normalized} =    norm_json.normalize_json_text    ${response.text}    jmes_path=${jmes_path}
744     RETURN    ${text_normalized}
745
746 Check_Status_Code
747     [Documentation]    Log response text, check status_code is one of allowed ones. In cases where this keyword is
748     ...    called in a WUKS it could end up logging tons of data and it may be desired to skip the logging by passing
749     ...    log_response=False, but by default it remains True.
750     [Arguments]    ${response}    ${additional_allowed_status_codes}=${NO_STATUS_CODES}    ${explicit_status_codes}=${NO_STATUS_CODES}    ${log_response}=True
751     # TODO: Remove overlap with keywords from Utils.robot
752     IF    "${log_response}" == "True"    BuiltIn.Log    ${response.text}
753     IF    "${log_response}" == "True"    BuiltIn.Log    ${response.status_code}
754     # In order to allow other existing keywords to consume this keyword by passing a single non-list status code, we need to
755     # check the type of the argument passed and convert those single non-list codes in to a one item list
756     ${status_codes_type} =    Evaluate    type($additional_allowed_status_codes).__name__
757     IF    "${status_codes_type}"!="list"
758         ${allowed_status_codes_list} =    Create List    ${additional_allowed_status_codes}
759     ELSE
760         ${allowed_status_codes_list} =    Set Variable    ${additional_allowed_status_codes}
761     END
762     ${status_codes_type} =    Evaluate    type($explicit_status_codes).__name__
763     IF    "${status_codes_type}"!="list"
764         ${explicit_status_codes_list} =    Create List    ${explicit_status_codes}
765     ELSE
766         ${explicit_status_codes_list} =    Set Variable    ${explicit_status_codes}
767     END
768     BuiltIn.Run_Keyword_And_Return_If
769     ...    """${explicit_status_codes_list}""" != """${NO_STATUS_CODES}"""
770     ...    Collections.List_Should_Contain_Value
771     ...    ${explicit_status_codes_list}
772     ...    ${response.status_code}
773     ${final_allowd_list} =    Collections.Combine_Lists    ${ALLOWED_STATUS_CODES}    ${allowed_status_codes_list}
774     Collections.List_Should_Contain_Value    ${final_allowd_list}    ${response.status_code}
775
776 Join_Two_Headers
777     [Documentation]    Take two dicts, join them, return result. Second argument values take precedence.
778     [Arguments]    ${first}    ${second}
779     ${accumulator} =    Collections.Copy_Dictionary    ${first}
780     ${items_to_add} =    Collections.Get_Dictionary_Items    ${second}
781     Collections.Set_To_Dictionary    ${accumulator}    @{items_to_add}
782     BuiltIn.Log    ${accumulator}
783     RETURN    ${accumulator}
784
785 Resolve_Text_From_Template_Folder
786     [Documentation]    Read a template from folder, strip endline, make changes according to mapping, return the result.
787     ...    If \${iterations} value is present, put text together from "prolog", "item" and "epilog" parts,
788     ...    where additional template variable ${i} goes from ${iter_start}, by one ${iterations} times.
789     ...    Template variable ${j} is calculated as ${i} incremented by offset ${iter_j_offset} ( j = i + iter_j_offset )
790     ...    used to create non uniform data in order to be able to validate UPDATE operations.
791     ...    POST (as opposed to PUT) needs slightly different data, \${name_prefix} may be used to distinguish.
792     ...    (Actually, it is GET who formats data differently when URI is a top-level container.)
793     [Arguments]    ${folder}    ${name_prefix}=${EMPTY}    ${base_name}=data    ${extension}=json    ${mapping}=${EMPTY}    ${iterations}=${EMPTY}
794     ...    ${iter_start}=1    ${iter_j_offset}=0    ${endline}=${\n}    ${percent_encode}=False
795     BuiltIn.Run_Keyword_And_Return_If
796     ...    not "${iterations}"
797     ...    Resolve_Text_From_Template_File
798     ...    folder=${folder}
799     ...    file_name=${name_prefix}${base_name}.${extension}
800     ...    mapping=${mapping}
801     ...    percent_encode=${percent_encode}
802     ${prolog} =    Resolve_Text_From_Template_File
803     ...    folder=${folder}
804     ...    file_name=${name_prefix}${base_name}.prolog.${extension}
805     ...    mapping=${mapping}
806     ...    percent_encode=${percent_encode}
807     ${epilog} =    Resolve_Text_From_Template_File
808     ...    folder=${folder}
809     ...    file_name=${name_prefix}${base_name}.epilog.${extension}
810     ...    mapping=${mapping}
811     ...    percent_encode=${percent_encode}
812     # Even POST uses the same item template (except indentation), so name prefix is ignored.
813     ${item_template} =    Resolve_Text_From_Template_File
814     ...    folder=${folder}
815     ...    file_name=${base_name}.item.${extension}
816     ...    mapping=${mapping}
817     ${items} =    BuiltIn.Create_List
818     ${separator} =    BuiltIn.Set_Variable_If    '${extension}' != 'json'    ${endline}    ,${endline}
819     FOR    ${iteration}    IN RANGE    ${iter_start}    ${iterations}+${iter_start}
820         IF    ${iteration} > ${iter_start}
821             Collections.Append_To_List    ${items}    ${separator}
822         END
823         ${j} =    BuiltIn.Evaluate    ${iteration}+${iter_j_offset}
824         ${item} =    BuiltIn.Evaluate
825         ...    string.Template('''${item_template}''').substitute({"i":"${iteration}", "j":${j}})
826         ...    modules=string
827         Collections.Append_To_List    ${items}    ${item}
828         # TODO: The following makes ugly result for iterations=0. Should we fix that?
829     END
830     ${final_text} =    BuiltIn.Catenate    SEPARATOR=    ${prolog}    ${endline}    @{items}    ${endline}
831     ...    ${epilog}
832     RETURN    ${final_text}
833
834 Resolve_Text_From_Template_File
835     [Documentation]    Check if ${folder}.${ODL_STREAM}/${file_name} exists. If yes read and Log contents of file ${folder}.${ODL_STREAM}/${file_name},
836     ...    remove endline, perform safe substitution, return result.
837     ...    If no do it with the default ${folder}/${file_name}.
838     [Arguments]    ${folder}    ${file_name}    ${mapping}=&{EMPTY}    ${percent_encode}=False
839     ${file_path_stream} =    BuiltIn.Set Variable    ${folder}.${ODL_STREAM}${/}${file_name}
840     ${file_stream_exists} =    BuiltIn.Run Keyword And Return Status
841     ...    OperatingSystem.File Should Exist
842     ...    ${file_path_stream}
843     ${file_path} =    BuiltIn.Set Variable If
844     ...    ${file_stream_exists}
845     ...    ${file_path_stream}
846     ...    ${folder}${/}${file_name}
847     ${template} =    OperatingSystem.Get_File    ${file_path}
848     BuiltIn.Log    ${template}
849     IF    ${percent_encode} == True
850         ${mapping_to_use} =    Encode_Mapping    ${mapping}
851     ELSE
852         ${mapping_to_use} =    BuiltIn.Set_Variable    ${mapping}
853     END
854     ${final_text} =    BuiltIn.Evaluate
855     ...    string.Template('''${template}'''.rstrip()).safe_substitute(${mapping_to_use})
856     ...    modules=string
857     RETURN    ${final_text}
858
859     # Final text is logged where used.
860
861 Normalize_Jsons_And_Compare
862     [Documentation]    Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
863     [Arguments]    ${expected_raw}    ${actual_raw}
864     ${expected_normalized} =    norm_json.normalize_json_text    ${expected_raw}
865     ${actual_normalized} =    norm_json.normalize_json_text    ${actual_raw}
866     # Should_Be_Equal shall print nice diff-style line comparison.
867     BuiltIn.Should_Be_Equal    ${expected_normalized}    ${actual_normalized}
868     # TODO: Add garbage collection? Check whether the temporary data accumulates.
869
870 Normalize_Jsons_With_Bits_And_Compare
871     [Documentation]    Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
872     [Arguments]    ${expected_raw}    ${actual_raw}    ${keys_with_bits}=${KEYS_WITH_BITS}
873     ${expected_normalized} =    norm_json.normalize_json_text    ${expected_raw}    keys_with_bits=${keys_with_bits}
874     ${actual_normalized} =    norm_json.normalize_json_text    ${actual_raw}    keys_with_bits=${keys_with_bits}
875     BuiltIn.Should_Be_Equal    ${expected_normalized}    ${actual_normalized}
876
877 Encode_Mapping
878     [Arguments]    ${mapping}
879     BuiltIn.Log    mapping: ${mapping}
880     ${encoded_mapping} =    BuiltIn.Create_Dictionary
881     FOR    ${key}    ${value}    IN    &{mapping}
882         ${encoded_value} =    Percent_Encode_String    ${value}
883         Collections.Set_To_Dictionary    ${encoded_mapping}    ${key}    ${encoded_value}
884     END
885     RETURN    ${encoded_mapping}
886
887 Percent_Encode_String
888     [Documentation]    Percent encodes reserved characters in the given string so it can be used as part of url.
889     [Arguments]    ${value}
890     ${encoded} =    String.Replace_String_Using_Regexp    ${value}    :    %3A
891     RETURN    ${encoded}