Merge "update docs build names in .gitignore"
[parser.git] / tosca2heat / tosca-parser / toscaparser / tests / test_toscatplvalidation.py
1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
2 #    not use this file except in compliance with the License. You may obtain
3 #    a copy of the License at
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
7 #    Unless required by applicable law or agreed to in writing, software
8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 #    License for the specific language governing permissions and limitations
11 #    under the License.
12
13 import os
14 import six
15
16 from toscaparser.common import exception
17 from toscaparser.imports import ImportsLoader
18 from toscaparser.nodetemplate import NodeTemplate
19 from toscaparser.parameters import Input
20 from toscaparser.parameters import Output
21 from toscaparser.policy import Policy
22 from toscaparser.relationship_template import RelationshipTemplate
23 from toscaparser.tests.base import TestCase
24 from toscaparser.topology_template import TopologyTemplate
25 from toscaparser.tosca_template import ToscaTemplate
26 from toscaparser.triggers import Triggers
27 from toscaparser.utils.gettextutils import _
28
29 import toscaparser.utils.yamlparser
30
31
32 class ToscaTemplateValidationTest(TestCase):
33
34     def test_well_defined_template(self):
35         tpl_path = os.path.join(
36             os.path.dirname(os.path.abspath(__file__)),
37             "data/tosca_single_instance_wordpress.yaml")
38         self.assertIsNotNone(ToscaTemplate(tpl_path))
39
40     def test_first_level_sections(self):
41         tpl_path = os.path.join(
42             os.path.dirname(os.path.abspath(__file__)),
43             "data/test_tosca_top_level_error1.yaml")
44         self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path)
45         exception.ExceptionCollector.assertExceptionMessage(
46             exception.MissingRequiredFieldError,
47             _('Template is missing required field '
48               '"tosca_definitions_version".'))
49
50         tpl_path = os.path.join(
51             os.path.dirname(os.path.abspath(__file__)),
52             "data/test_tosca_top_level_error2.yaml")
53         self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path)
54         exception.ExceptionCollector.assertExceptionMessage(
55             exception.UnknownFieldError,
56             _('Template contains unknown field "node_template". Refer to the '
57               'definition to verify valid values.'))
58
59     def test_template_with_imports_validation(self):
60         tpl_path = os.path.join(
61             os.path.dirname(os.path.abspath(__file__)),
62             "data/tosca_imports_validation.yaml")
63         self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path)
64         exception.ExceptionCollector.assertExceptionMessage(
65             exception.UnknownFieldError,
66             _('Template custom_types/imported_sample.yaml contains unknown '
67               'field "descriptions". Refer to the definition'
68               ' to verify valid values.'))
69         exception.ExceptionCollector.assertExceptionMessage(
70             exception.UnknownFieldError,
71             _('Template custom_types/imported_sample.yaml contains unknown '
72               'field "node_typess". Refer to the definition to '
73               'verify valid values.'))
74         exception.ExceptionCollector.assertExceptionMessage(
75             exception.UnknownFieldError,
76             _('Template custom_types/imported_sample.yaml contains unknown '
77               'field "tosca1_definitions_version". Refer to the definition'
78               ' to verify valid values.'))
79         exception.ExceptionCollector.assertExceptionMessage(
80             exception.InvalidTemplateVersion,
81             _('The template version "tosca_simple_yaml_1_10 in '
82               'custom_types/imported_sample.yaml" is invalid. '
83               'Valid versions are "tosca_simple_yaml_1_0, '
84               'tosca_simple_profile_for_nfv_1_0_0".'))
85         exception.ExceptionCollector.assertExceptionMessage(
86             exception.UnknownFieldError,
87             _('Template custom_types/imported_sample.yaml contains unknown '
88               'field "policy_types1". Refer to the definition to '
89               'verify valid values.'))
90         exception.ExceptionCollector.assertExceptionMessage(
91             exception.UnknownFieldError,
92             _('Nodetype"tosca.nodes.SoftwareComponent.Logstash" contains '
93               'unknown field "capabilities1". Refer to the definition '
94               'to verify valid values.'))
95         exception.ExceptionCollector.assertExceptionMessage(
96             exception.UnknownFieldError,
97             _('Policy "mycompany.mytypes.myScalingPolicy" contains unknown '
98               'field "derived1_from". Refer to the definition to '
99               'verify valid values.'))
100
101     def test_inputs(self):
102         tpl_snippet = '''
103         inputs:
104           cpus:
105             type: integer
106             description: Number of CPUs for the server.
107             constraint:
108               - valid_values: [ 1, 2, 4, 8 ]
109         '''
110         inputs = (toscaparser.utils.yamlparser.
111                   simple_parse(tpl_snippet)['inputs'])
112         name, attrs = list(inputs.items())[0]
113         input = Input(name, attrs)
114         err = self.assertRaises(exception.UnknownFieldError, input.validate)
115         self.assertEqual(_('Input "cpus" contains unknown field "constraint". '
116                            'Refer to the definition to verify valid values.'),
117                          err.__str__())
118
119     def _imports_content_test(self, tpl_snippet, path, custom_type_def):
120         imports = (toscaparser.utils.yamlparser.
121                    simple_parse(tpl_snippet)['imports'])
122         loader = ImportsLoader(imports, path, custom_type_def)
123         return loader.get_custom_defs()
124
125     def test_imports_without_templates(self):
126         tpl_snippet = '''
127         imports:
128           # omitted here for brevity
129         '''
130         path = 'toscaparser/tests/data/tosca_elk.yaml'
131         errormsg = _('"imports" keyname is defined without including '
132                      'templates.')
133         err = self.assertRaises(exception.ValidationError,
134                                 self._imports_content_test,
135                                 tpl_snippet,
136                                 path,
137                                 "node_types")
138         self.assertEqual(errormsg, err.__str__())
139
140     def test_imports_with_name_without_templates(self):
141         tpl_snippet = '''
142         imports:
143           - some_definitions:
144         '''
145         path = 'toscaparser/tests/data/tosca_elk.yaml'
146         errormsg = _('A template file name is not provided with import '
147                      'definition "some_definitions".')
148         err = self.assertRaises(exception.ValidationError,
149                                 self._imports_content_test,
150                                 tpl_snippet, path, None)
151         self.assertEqual(errormsg, err.__str__())
152
153     def test_imports_without_import_name(self):
154         tpl_snippet = '''
155         imports:
156           - custom_types/paypalpizzastore_nodejs_app.yaml
157           - https://raw.githubusercontent.com/openstack/\
158 tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml
159         '''
160         path = 'toscaparser/tests/data/tosca_elk.yaml'
161         custom_defs = self._imports_content_test(tpl_snippet,
162                                                  path,
163                                                  "node_types")
164         self.assertTrue(custom_defs)
165
166     def test_imports_wth_import_name(self):
167         tpl_snippet = '''
168         imports:
169           - some_definitions: custom_types/paypalpizzastore_nodejs_app.yaml
170           - more_definitions:
171               file: 'https://raw.githubusercontent.com/openstack/tosca-parser\
172 /master/toscaparser/tests/data/custom_types/wordpress.yaml'
173               namespace_prefix: single_instance_wordpress
174         '''
175         path = 'toscaparser/tests/data/tosca_elk.yaml'
176         custom_defs = self._imports_content_test(tpl_snippet,
177                                                  path,
178                                                  "node_types")
179         self.assertTrue(custom_defs.get("single_instance_wordpress.tosca."
180                                         "nodes.WebApplication.WordPress"))
181
182     def test_imports_wth_namespace_prefix(self):
183         tpl_snippet = '''
184         imports:
185           - more_definitions:
186               file: custom_types/nested_rsyslog.yaml
187               namespace_prefix: testprefix
188         '''
189         path = 'toscaparser/tests/data/tosca_elk.yaml'
190         custom_defs = self._imports_content_test(tpl_snippet,
191                                                  path,
192                                                  "node_types")
193         self.assertTrue(custom_defs.get("testprefix.Rsyslog"))
194
195     def test_imports_with_no_main_template(self):
196         tpl_snippet = '''
197         imports:
198           - some_definitions: https://raw.githubusercontent.com/openstack/\
199 tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml
200           - some_definitions:
201               file: my_defns/my_typesdefs_n.yaml
202         '''
203         errormsg = _('Input tosca template is not provided.')
204         err = self.assertRaises(exception.ValidationError,
205                                 self._imports_content_test,
206                                 tpl_snippet, None, None)
207         self.assertEqual(errormsg, err.__str__())
208
209     def test_imports_duplicate_name(self):
210         tpl_snippet = '''
211         imports:
212           - some_definitions: https://raw.githubusercontent.com/openstack/\
213 tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml
214           - some_definitions:
215               file: my_defns/my_typesdefs_n.yaml
216         '''
217         errormsg = _('Duplicate import name "some_definitions" was found.')
218         path = 'toscaparser/tests/data/tosca_elk.yaml'
219         err = self.assertRaises(exception.ValidationError,
220                                 self._imports_content_test,
221                                 tpl_snippet, path, None)
222         self.assertEqual(errormsg, err.__str__())
223
224     def test_imports_missing_req_field_in_def(self):
225         tpl_snippet = '''
226         imports:
227           - more_definitions:
228               file1: my_defns/my_typesdefs_n.yaml
229               repository: my_company_repo
230               namespace_uri: http://mycompany.com/ns/tosca/2.0
231               namespace_prefix: mycompany
232         '''
233         errormsg = _('Import of template "more_definitions" is missing '
234                      'required field "file".')
235         path = 'toscaparser/tests/data/tosca_elk.yaml'
236         err = self.assertRaises(exception.MissingRequiredFieldError,
237                                 self._imports_content_test,
238                                 tpl_snippet, path, None)
239         self.assertEqual(errormsg, err.__str__())
240
241     def test_imports_file_with_uri(self):
242         tpl_snippet = '''
243         imports:
244           - more_definitions:
245               file: https://raw.githubusercontent.com/openstack/\
246 tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml
247         '''
248         path = 'https://raw.githubusercontent.com/openstack/\
249 tosca-parser/master/toscaparser/tests/data/\
250 tosca_single_instance_wordpress_with_url_import.yaml'
251         custom_defs = self._imports_content_test(tpl_snippet,
252                                                  path,
253                                                  "node_types")
254         self.assertTrue(custom_defs.get("tosca.nodes."
255                                         "WebApplication.WordPress"))
256
257     def test_imports_file_namespace_fields(self):
258         tpl_snippet = '''
259         imports:
260           - more_definitions:
261              file: https://raw.githubusercontent.com/openstack/\
262 heat-translator/master/translator/tests/data/custom_types/wordpress.yaml
263              namespace_prefix: mycompany
264              namespace_uri: http://docs.oasis-open.org/tosca/ns/simple/yaml/1.0
265         '''
266         path = 'toscaparser/tests/data/tosca_elk.yaml'
267         custom_defs = self._imports_content_test(tpl_snippet,
268                                                  path,
269                                                  "node_types")
270         self.assertTrue(custom_defs.get("mycompany.tosca.nodes."
271                                         "WebApplication.WordPress"))
272
273     def test_import_error_file_uri(self):
274         tpl_snippet = '''
275         imports:
276           - more_definitions:
277              file: mycompany.com/ns/tosca/2.0/toscaparser/tests/data\
278 /tosca_elk.yaml
279              namespace_prefix: mycompany
280              namespace_uri: http://docs.oasis-open.org/tosca/ns/simple/yaml/1.0
281         '''
282         path = 'toscaparser/tests/data/tosca_elk.yaml'
283         self.assertRaises(ImportError,
284                           self._imports_content_test,
285                           tpl_snippet, path, None)
286
287     def test_import_single_line_error(self):
288         tpl_snippet = '''
289         imports:
290           - some_definitions: abc.com/tests/data/tosca_elk.yaml
291         '''
292         errormsg = _('Import "abc.com/tests/data/tosca_elk.yaml" is not '
293                      'valid.')
294         path = 'toscaparser/tests/data/tosca_elk.yaml'
295         err = self.assertRaises(ImportError,
296                                 self._imports_content_test,
297                                 tpl_snippet, path, None)
298         self.assertEqual(errormsg, err.__str__())
299
300     def test_outputs(self):
301         tpl_snippet = '''
302         outputs:
303           server_address:
304             description: IP address of server instance.
305             values: { get_property: [server, private_address] }
306         '''
307         outputs = (toscaparser.utils.yamlparser.
308                    simple_parse(tpl_snippet)['outputs'])
309         name, attrs = list(outputs.items())[0]
310         output = Output(name, attrs)
311         try:
312             output.validate()
313         except Exception as err:
314             self.assertTrue(
315                 isinstance(err, exception.MissingRequiredFieldError))
316             self.assertEqual(_('Output "server_address" is missing required '
317                                'field "value".'), err.__str__())
318
319         tpl_snippet = '''
320         outputs:
321           server_address:
322             descriptions: IP address of server instance.
323             value: { get_property: [server, private_address] }
324         '''
325         outputs = (toscaparser.utils.yamlparser.
326                    simple_parse(tpl_snippet)['outputs'])
327         name, attrs = list(outputs.items())[0]
328         output = Output(name, attrs)
329         try:
330             output.validate()
331         except Exception as err:
332             self.assertTrue(isinstance(err, exception.UnknownFieldError))
333             self.assertEqual(_('Output "server_address" contains unknown '
334                                'field "descriptions". Refer to the definition '
335                                'to verify valid values.'),
336                              err.__str__())
337
338     def test_groups(self):
339         tpl_snippet = '''
340         node_templates:
341           server:
342             type: tosca.nodes.Compute
343             requirements:
344               - log_endpoint:
345                   capability: log_endpoint
346
347           mysql_dbms:
348             type: tosca.nodes.DBMS
349             properties:
350               root_password: aaa
351               port: 3376
352
353         groups:
354           webserver_group:
355             type: tosca.groups.Root
356             members: [ server, mysql_dbms ]
357         '''
358         tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
359         TopologyTemplate(tpl, None)
360
361     def test_groups_with_missing_required_field(self):
362         tpl_snippet = '''
363         node_templates:
364           server:
365             type: tosca.nodes.Compute
366             requirements:
367               - log_endpoint:
368                   capability: log_endpoint
369
370           mysql_dbms:
371             type: tosca.nodes.DBMS
372             properties:
373               root_password: aaa
374               port: 3376
375
376         groups:
377           webserver_group:
378               members: ['server', 'mysql_dbms']
379         '''
380         tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
381         err = self.assertRaises(exception.MissingRequiredFieldError,
382                                 TopologyTemplate, tpl, None)
383         expectedmessage = _('Template "webserver_group" is missing '
384                             'required field "type".')
385         self.assertEqual(expectedmessage, err.__str__())
386
387     def test_groups_with_unknown_target(self):
388         tpl_snippet = '''
389         node_templates:
390           server:
391             type: tosca.nodes.Compute
392             requirements:
393               - log_endpoint:
394                   capability: log_endpoint
395
396           mysql_dbms:
397             type: tosca.nodes.DBMS
398             properties:
399               root_password: aaa
400               port: 3376
401
402         groups:
403           webserver_group:
404             type: tosca.groups.Root
405             members: [ serv, mysql_dbms ]
406         '''
407         tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
408         expectedmessage = _('"Target member "serv" is not found in '
409                             'node_templates"')
410         err = self.assertRaises(exception.InvalidGroupTargetException,
411                                 TopologyTemplate, tpl, None)
412         self.assertEqual(expectedmessage, err.__str__())
413
414     def test_groups_with_repeated_targets(self):
415         tpl_snippet = '''
416         node_templates:
417           server:
418             type: tosca.nodes.Compute
419             requirements:
420               - log_endpoint:
421                   capability: log_endpoint
422
423           mysql_dbms:
424             type: tosca.nodes.DBMS
425             properties:
426               root_password: aaa
427               port: 3376
428
429         groups:
430           webserver_group:
431             type: tosca.groups.Root
432             members: [ server, server, mysql_dbms ]
433         '''
434         tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
435         expectedmessage = _('"Member nodes '
436                             '"[\'server\', \'server\', \'mysql_dbms\']" '
437                             'should be >= 1 and not repeated"')
438         err = self.assertRaises(exception.InvalidGroupTargetException,
439                                 TopologyTemplate, tpl, None)
440         self.assertEqual(expectedmessage, err.__str__())
441
442     def test_groups_with_only_one_target(self):
443         tpl_snippet = '''
444         node_templates:
445           server:
446             type: tosca.nodes.Compute
447             requirements:
448               - log_endpoint:
449                   capability: log_endpoint
450
451           mysql_dbms:
452             type: tosca.nodes.DBMS
453             properties:
454               root_password: aaa
455               port: 3376
456
457         groups:
458           webserver_group:
459             type: tosca.groups.Root
460             members: []
461         '''
462         tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
463         expectedmessage = _('"Member nodes "[]" should be >= 1 '
464                             'and not repeated"')
465         err = self.assertRaises(exception.InvalidGroupTargetException,
466                                 TopologyTemplate, tpl, None)
467         self.assertEqual(expectedmessage, err.__str__())
468
469     def _custom_types(self):
470         custom_types = {}
471         def_file = os.path.join(
472             os.path.dirname(os.path.abspath(__file__)),
473             "data/custom_types/wordpress.yaml")
474         custom_type = toscaparser.utils.yamlparser.load_yaml(def_file)
475         node_types = custom_type['node_types']
476         for name in node_types:
477             defintion = node_types[name]
478             custom_types[name] = defintion
479         return custom_types
480
481     def _single_node_template_content_test(self, tpl_snippet):
482         nodetemplates = (toscaparser.utils.yamlparser.
483                          simple_ordered_parse(tpl_snippet))['node_templates']
484         name = list(nodetemplates.keys())[0]
485         nodetemplate = NodeTemplate(name, nodetemplates,
486                                     self._custom_types())
487         nodetemplate.validate()
488         nodetemplate.requirements
489         nodetemplate.get_capabilities_objects()
490         nodetemplate.get_properties_objects()
491         nodetemplate.interfaces
492
493     def test_node_templates(self):
494         tpl_snippet = '''
495         node_templates:
496           server:
497             capabilities:
498               host:
499                 properties:
500                   disk_size: 10
501                   num_cpus: 4
502                   mem_size: 4096
503               os:
504                 properties:
505                   architecture: x86_64
506                   type: Linux
507                   distribution: Fedora
508                   version: 18.0
509         '''
510         expectedmessage = _('Template "server" is missing required field '
511                             '"type".')
512         err = self.assertRaises(
513             exception.MissingRequiredFieldError,
514             lambda: self._single_node_template_content_test(tpl_snippet))
515         self.assertEqual(expectedmessage, err.__str__())
516
517     def test_node_template_with_wrong_properties_keyname(self):
518         """Node template keyname 'properties' given as 'propertiessss'."""
519         tpl_snippet = '''
520         node_templates:
521           mysql_dbms:
522             type: tosca.nodes.DBMS
523             propertiessss:
524               root_password: aaa
525               port: 3376
526         '''
527         expectedmessage = _('Node template "mysql_dbms" contains unknown '
528                             'field "propertiessss". Refer to the definition '
529                             'to verify valid values.')
530         err = self.assertRaises(
531             exception.UnknownFieldError,
532             lambda: self._single_node_template_content_test(tpl_snippet))
533         self.assertEqual(expectedmessage, err.__str__())
534
535     def test_node_template_with_wrong_requirements_keyname(self):
536         """Node template keyname 'requirements' given as 'requirement'."""
537         tpl_snippet = '''
538         node_templates:
539           mysql_dbms:
540             type: tosca.nodes.DBMS
541             properties:
542               root_password: aaa
543               port: 3376
544             requirement:
545               - host: server
546         '''
547         expectedmessage = _('Node template "mysql_dbms" contains unknown '
548                             'field "requirement". Refer to the definition to '
549                             'verify valid values.')
550         err = self.assertRaises(
551             exception.UnknownFieldError,
552             lambda: self._single_node_template_content_test(tpl_snippet))
553         self.assertEqual(expectedmessage, err.__str__())
554
555     def test_node_template_with_wrong_interfaces_keyname(self):
556         """Node template keyname 'interfaces' given as 'interfac'."""
557         tpl_snippet = '''
558         node_templates:
559           mysql_dbms:
560             type: tosca.nodes.DBMS
561             properties:
562               root_password: aaa
563               port: 3376
564             requirements:
565               - host: server
566             interfac:
567               Standard:
568                 configure: mysql_database_configure.sh
569         '''
570         expectedmessage = _('Node template "mysql_dbms" contains unknown '
571                             'field "interfac". Refer to the definition to '
572                             'verify valid values.')
573         err = self.assertRaises(
574             exception.UnknownFieldError,
575             lambda: self._single_node_template_content_test(tpl_snippet))
576         self.assertEqual(expectedmessage, err.__str__())
577
578     def test_node_template_with_wrong_capabilities_keyname(self):
579         """Node template keyname 'capabilities' given as 'capabilitiis'."""
580         tpl_snippet = '''
581         node_templates:
582           mysql_database:
583             type: tosca.nodes.Database
584             properties:
585               db_name: { get_input: db_name }
586               db_user: { get_input: db_user }
587               db_password: { get_input: db_pwd }
588             capabilitiis:
589               database_endpoint:
590                 properties:
591                   port: { get_input: db_port }
592         '''
593         expectedmessage = _('Node template "mysql_database" contains unknown '
594                             'field "capabilitiis". Refer to the definition to '
595                             'verify valid values.')
596         err = self.assertRaises(
597             exception.UnknownFieldError,
598             lambda: self._single_node_template_content_test(tpl_snippet))
599         self.assertEqual(expectedmessage, err.__str__())
600
601     def test_node_template_with_wrong_artifacts_keyname(self):
602         """Node template keyname 'artifacts' given as 'artifactsss'."""
603         tpl_snippet = '''
604         node_templates:
605           mysql_database:
606             type: tosca.nodes.Database
607             artifactsss:
608               db_content:
609                 implementation: files/my_db_content.txt
610                 type: tosca.artifacts.File
611         '''
612         expectedmessage = _('Node template "mysql_database" contains unknown '
613                             'field "artifactsss". Refer to the definition to '
614                             'verify valid values.')
615         err = self.assertRaises(
616             exception.UnknownFieldError,
617             lambda: self._single_node_template_content_test(tpl_snippet))
618         self.assertEqual(expectedmessage, err.__str__())
619
620     def test_node_template_with_multiple_wrong_keynames(self):
621         """Node templates given with multiple wrong keynames."""
622         tpl_snippet = '''
623         node_templates:
624           mysql_dbms:
625             type: tosca.nodes.DBMS
626             propertieees:
627               root_password: aaa
628               port: 3376
629             requirements:
630               - host: server
631             interfacs:
632               Standard:
633                 configure: mysql_database_configure.sh
634         '''
635         expectedmessage = _('Node template "mysql_dbms" contains unknown '
636                             'field "propertieees". Refer to the definition to '
637                             'verify valid values.')
638         err = self.assertRaises(
639             exception.UnknownFieldError,
640             lambda: self._single_node_template_content_test(tpl_snippet))
641         self.assertEqual(expectedmessage, err.__str__())
642
643         tpl_snippet = '''
644         node_templates:
645           mysql_database:
646             type: tosca.nodes.Database
647             properties:
648               name: { get_input: db_name }
649               user: { get_input: db_user }
650               password: { get_input: db_pwd }
651             capabilitiiiies:
652               database_endpoint:
653               properties:
654                 port: { get_input: db_port }
655             requirementsss:
656               - host:
657                   node: mysql_dbms
658             interfac:
659               Standard:
660                  configure: mysql_database_configure.sh
661
662         '''
663         expectedmessage = _('Node template "mysql_database" contains unknown '
664                             'field "capabilitiiiies". Refer to the definition '
665                             'to verify valid values.')
666         err = self.assertRaises(
667             exception.UnknownFieldError,
668             lambda: self._single_node_template_content_test(tpl_snippet))
669         self.assertEqual(expectedmessage, err.__str__())
670
671     def test_node_template_type(self):
672         tpl_snippet = '''
673         node_templates:
674           mysql_database:
675             type: tosca.nodes.Databases
676             properties:
677               db_name: { get_input: db_name }
678               db_user: { get_input: db_user }
679               db_password: { get_input: db_pwd }
680             capabilities:
681               database_endpoint:
682                 properties:
683                   port: { get_input: db_port }
684             requirements:
685               - host: mysql_dbms
686             interfaces:
687               Standard:
688                  configure: mysql_database_configure.sh
689         '''
690         expectedmessage = _('Type "tosca.nodes.Databases" is not '
691                             'a valid type.')
692         err = self.assertRaises(
693             exception.InvalidTypeError,
694             lambda: self._single_node_template_content_test(tpl_snippet))
695         self.assertEqual(expectedmessage, err.__str__())
696
697     def test_node_template_requirements(self):
698         tpl_snippet = '''
699         node_templates:
700           webserver:
701             type: tosca.nodes.WebServer
702             requirements:
703               host: server
704             interfaces:
705               Standard:
706                 create: webserver_install.sh
707                 start: d.sh
708         '''
709         expectedmessage = _('"requirements" of template "webserver" must be '
710                             'of type "list".')
711         err = self.assertRaises(
712             exception.TypeMismatchError,
713             lambda: self._single_node_template_content_test(tpl_snippet))
714         self.assertEqual(expectedmessage, err.__str__())
715
716         tpl_snippet = '''
717         node_templates:
718           mysql_database:
719             type: tosca.nodes.Database
720             properties:
721               db_name: { get_input: db_name }
722               db_user: { get_input: db_user }
723               db_password: { get_input: db_pwd }
724             capabilities:
725               database_endpoint:
726                 properties:
727                   port: { get_input: db_port }
728             requirements:
729               - host: mysql_dbms
730               - database_endpoint: mysql_database
731             interfaces:
732               Standard:
733                  configure: mysql_database_configure.sh
734         '''
735         expectedmessage = _('"requirements" of template "mysql_database" '
736                             'contains unknown field "database_endpoint". '
737                             'Refer to the definition to verify valid values.')
738         err = self.assertRaises(
739             exception.UnknownFieldError,
740             lambda: self._single_node_template_content_test(tpl_snippet))
741         self.assertEqual(expectedmessage, err.__str__())
742
743     def test_node_template_requirements_with_wrong_node_keyname(self):
744         """Node template requirements keyname 'node' given as 'nodes'."""
745         tpl_snippet = '''
746         node_templates:
747           mysql_database:
748             type: tosca.nodes.Database
749             requirements:
750               - host:
751                   nodes: mysql_dbms
752
753         '''
754         expectedmessage = _('"requirements" of template "mysql_database" '
755                             'contains unknown field "nodes". Refer to the '
756                             'definition to verify valid values.')
757         err = self.assertRaises(
758             exception.UnknownFieldError,
759             lambda: self._single_node_template_content_test(tpl_snippet))
760         self.assertEqual(expectedmessage, err.__str__())
761
762     def test_node_template_requirements_with_wrong_capability_keyname(self):
763         """Incorrect node template requirements keyname
764
765         Node template requirements keyname 'capability' given as
766         'capabilityy'.
767         """
768         tpl_snippet = '''
769         node_templates:
770           mysql_database:
771             type: tosca.nodes.Database
772             requirements:
773               - host:
774                   node: mysql_dbms
775               - log_endpoint:
776                   node: logstash
777                   capabilityy: log_endpoint
778                   relationship:
779                     type: tosca.relationships.ConnectsTo
780
781         '''
782         expectedmessage = _('"requirements" of template "mysql_database" '
783                             'contains unknown field "capabilityy". Refer to '
784                             'the definition to verify valid values.')
785         err = self.assertRaises(
786             exception.UnknownFieldError,
787             lambda: self._single_node_template_content_test(tpl_snippet))
788         self.assertEqual(expectedmessage, err.__str__())
789
790     def test_node_template_requirements_with_wrong_relationship_keyname(self):
791         """Incorrect node template requirements keyname
792
793         Node template requirements keyname 'relationship' given as
794         'relationshipppp'.
795         """
796         tpl_snippet = '''
797         node_templates:
798           mysql_database:
799             type: tosca.nodes.Database
800             requirements:
801               - host:
802                   node: mysql_dbms
803               - log_endpoint:
804                   node: logstash
805                   capability: log_endpoint
806                   relationshipppp:
807                     type: tosca.relationships.ConnectsTo
808
809         '''
810         expectedmessage = _('"requirements" of template "mysql_database" '
811                             'contains unknown field "relationshipppp". Refer '
812                             'to the definition to verify valid values.')
813         err = self.assertRaises(
814             exception.UnknownFieldError,
815             lambda: self._single_node_template_content_test(tpl_snippet))
816         self.assertEqual(expectedmessage, err.__str__())
817
818     def test_node_template_requirements_with_wrong_occurrences_keyname(self):
819         """Incorrect node template requirements keyname
820
821         Node template requirements keyname 'occurrences' given as
822         'occurences'.
823         """
824         tpl_snippet = '''
825         node_templates:
826           mysql_database:
827             type: tosca.nodes.Database
828             requirements:
829               - host:
830                   node: mysql_dbms
831               - log_endpoint:
832                   node: logstash
833                   capability: log_endpoint
834                   relationship:
835                     type: tosca.relationships.ConnectsTo
836                   occurences: [0, UNBOUNDED]
837         '''
838         expectedmessage = _('"requirements" of template "mysql_database" '
839                             'contains unknown field "occurences". Refer to '
840                             'the definition to verify valid values.')
841         err = self.assertRaises(
842             exception.UnknownFieldError,
843             lambda: self._single_node_template_content_test(tpl_snippet))
844         self.assertEqual(expectedmessage, err.__str__())
845
846     def test_node_template_requirements_with_multiple_wrong_keynames(self):
847         """Node templates given with multiple wrong requirements keynames."""
848         tpl_snippet = '''
849         node_templates:
850           mysql_database:
851             type: tosca.nodes.Database
852             requirements:
853               - host:
854                   node: mysql_dbms
855               - log_endpoint:
856                   nod: logstash
857                   capabilit: log_endpoint
858                   relationshipppp:
859                     type: tosca.relationships.ConnectsTo
860
861         '''
862         expectedmessage = _('"requirements" of template "mysql_database" '
863                             'contains unknown field "nod". Refer to the '
864                             'definition to verify valid values.')
865         err = self.assertRaises(
866             exception.UnknownFieldError,
867             lambda: self._single_node_template_content_test(tpl_snippet))
868         self.assertEqual(expectedmessage, err.__str__())
869
870         tpl_snippet = '''
871         node_templates:
872           mysql_database:
873             type: tosca.nodes.Database
874             requirements:
875               - host:
876                   node: mysql_dbms
877               - log_endpoint:
878                   node: logstash
879                   capabilit: log_endpoint
880                   relationshipppp:
881                     type: tosca.relationships.ConnectsTo
882
883         '''
884         expectedmessage = _('"requirements" of template "mysql_database" '
885                             'contains unknown field "capabilit". Refer to the '
886                             'definition to verify valid values.')
887         err = self.assertRaises(
888             exception.UnknownFieldError,
889             lambda: self._single_node_template_content_test(tpl_snippet))
890         self.assertEqual(expectedmessage, err.__str__())
891
892     def test_node_template_requirements_invalid_occurrences(self):
893         tpl_snippet = '''
894         node_templates:
895           server:
896             type: tosca.nodes.Compute
897             requirements:
898               - log_endpoint:
899                   capability: log_endpoint
900                   occurrences: [0, -1]
901         '''
902         expectedmessage = _('Value of property "[0, -1]" is invalid.')
903         err = self.assertRaises(
904             exception.InvalidPropertyValueError,
905             lambda: self._single_node_template_content_test(tpl_snippet))
906         self.assertEqual(expectedmessage, err.__str__())
907
908         tpl_snippet = '''
909         node_templates:
910           server:
911             type: tosca.nodes.Compute
912             requirements:
913               - log_endpoint:
914                   capability: log_endpoint
915                   occurrences: [a, w]
916         '''
917         expectedmessage = _('"a" is not an integer.')
918         err = self.assertRaises(
919             ValueError,
920             lambda: self._single_node_template_content_test(tpl_snippet))
921         self.assertEqual(expectedmessage, err.__str__())
922
923         tpl_snippet = '''
924         node_templates:
925           server:
926             type: tosca.nodes.Compute
927             requirements:
928               - log_endpoint:
929                   capability: log_endpoint
930                   occurrences: -1
931         '''
932         expectedmessage = _('"-1" is not a list.')
933         err = self.assertRaises(
934             ValueError,
935             lambda: self._single_node_template_content_test(tpl_snippet))
936         self.assertEqual(expectedmessage, err.__str__())
937
938         tpl_snippet = '''
939         node_templates:
940           server:
941             type: tosca.nodes.Compute
942             requirements:
943               - log_endpoint:
944                   capability: log_endpoint
945                   occurrences: [5, 1]
946         '''
947         expectedmessage = _('Value of property "[5, 1]" is invalid.')
948         err = self.assertRaises(
949             exception.InvalidPropertyValueError,
950             lambda: self._single_node_template_content_test(tpl_snippet))
951         self.assertEqual(expectedmessage, err.__str__())
952
953         tpl_snippet = '''
954         node_templates:
955           server:
956             type: tosca.nodes.Compute
957             requirements:
958               - log_endpoint:
959                   capability: log_endpoint
960                   occurrences: [0, 0]
961         '''
962         expectedmessage = _('Value of property "[0, 0]" is invalid.')
963         err = self.assertRaises(
964             exception.InvalidPropertyValueError,
965             lambda: self._single_node_template_content_test(tpl_snippet))
966         self.assertEqual(expectedmessage, err.__str__())
967
968     def test_node_template_requirements_valid_occurrences(self):
969         tpl_snippet = '''
970         node_templates:
971           server:
972             type: tosca.nodes.Compute
973             requirements:
974               - log_endpoint:
975                   capability: log_endpoint
976                   occurrences: [2, 2]
977         '''
978         self._single_node_template_content_test(tpl_snippet)
979
980     def test_node_template_capabilities(self):
981         tpl_snippet = '''
982         node_templates:
983           mysql_database:
984             type: tosca.nodes.Database
985             properties:
986               db_name: { get_input: db_name }
987               db_user: { get_input: db_user }
988               db_password: { get_input: db_pwd }
989             capabilities:
990               http_endpoint:
991                 properties:
992                   port: { get_input: db_port }
993             requirements:
994               - host: mysql_dbms
995             interfaces:
996               Standard:
997                  configure: mysql_database_configure.sh
998         '''
999         expectedmessage = _('"capabilities" of template "mysql_database" '
1000                             'contains unknown field "http_endpoint". Refer to '
1001                             'the definition to verify valid values.')
1002         err = self.assertRaises(
1003             exception.UnknownFieldError,
1004             lambda: self._single_node_template_content_test(tpl_snippet))
1005         self.assertEqual(expectedmessage, err.__str__())
1006
1007     def test_node_template_properties(self):
1008         tpl_snippet = '''
1009         node_templates:
1010           server:
1011             type: tosca.nodes.Compute
1012             properties:
1013               os_image: F18_x86_64
1014             capabilities:
1015               host:
1016                 properties:
1017                   disk_size: 10 GB
1018                   num_cpus: { get_input: cpus }
1019                   mem_size: 4096 MB
1020               os:
1021                 properties:
1022                   architecture: x86_64
1023                   type: Linux
1024                   distribution: Fedora
1025                   version: 18.0
1026         '''
1027         expectedmessage = _('"properties" of template "server" contains '
1028                             'unknown field "os_image". Refer to the '
1029                             'definition to verify valid values.')
1030         err = self.assertRaises(
1031             exception.UnknownFieldError,
1032             lambda: self._single_node_template_content_test(tpl_snippet))
1033         self.assertEqual(expectedmessage, err.__str__())
1034
1035     def test_node_template_interfaces(self):
1036         tpl_snippet = '''
1037         node_templates:
1038           wordpress:
1039             type: tosca.nodes.WebApplication.WordPress
1040             requirements:
1041               - host: webserver
1042               - database_endpoint: mysql_database
1043             interfaces:
1044               Standards:
1045                  create: wordpress_install.sh
1046                  configure:
1047                    implementation: wordpress_configure.sh
1048                    inputs:
1049                      wp_db_name: { get_property: [ mysql_database, db_name ] }
1050                      wp_db_user: { get_property: [ mysql_database, db_user ] }
1051                      wp_db_password: { get_property: [ mysql_database, \
1052                      db_password ] }
1053                      wp_db_port: { get_property: [ SELF, \
1054                      database_endpoint, port ] }
1055         '''
1056         expectedmessage = _('"interfaces" of template "wordpress" contains '
1057                             'unknown field "Standards". Refer to the '
1058                             'definition to verify valid values.')
1059         err = self.assertRaises(
1060             exception.UnknownFieldError,
1061             lambda: self._single_node_template_content_test(tpl_snippet))
1062         self.assertEqual(expectedmessage, err.__str__())
1063
1064         tpl_snippet = '''
1065         node_templates:
1066           wordpress:
1067             type: tosca.nodes.WebApplication.WordPress
1068             requirements:
1069               - host: webserver
1070               - database_endpoint: mysql_database
1071             interfaces:
1072               Standard:
1073                  create: wordpress_install.sh
1074                  config:
1075                    implementation: wordpress_configure.sh
1076                    inputs:
1077                      wp_db_name: { get_property: [ mysql_database, db_name ] }
1078                      wp_db_user: { get_property: [ mysql_database, db_user ] }
1079                      wp_db_password: { get_property: [ mysql_database, \
1080                      db_password ] }
1081                      wp_db_port: { get_property: [ SELF, \
1082                      database_endpoint, port ] }
1083         '''
1084         expectedmessage = _('"interfaces" of template "wordpress" contains '
1085                             'unknown field "config". Refer to the definition '
1086                             'to verify valid values.')
1087         err = self.assertRaises(
1088             exception.UnknownFieldError,
1089             lambda: self._single_node_template_content_test(tpl_snippet))
1090         self.assertEqual(expectedmessage, err.__str__())
1091
1092         tpl_snippet = '''
1093         node_templates:
1094           wordpress:
1095             type: tosca.nodes.WebApplication.WordPress
1096             requirements:
1097               - host: webserver
1098               - database_endpoint: mysql_database
1099             interfaces:
1100               Standard:
1101                  create: wordpress_install.sh
1102                  configure:
1103                    implementation: wordpress_configure.sh
1104                    input:
1105                      wp_db_name: { get_property: [ mysql_database, db_name ] }
1106                      wp_db_user: { get_property: [ mysql_database, db_user ] }
1107                      wp_db_password: { get_property: [ mysql_database, \
1108                      db_password ] }
1109                      wp_db_port: { get_ref_property: [ database_endpoint, \
1110                      database_endpoint, port ] }
1111         '''
1112         expectedmessage = _('"interfaces" of template "wordpress" contains '
1113                             'unknown field "input". Refer to the definition '
1114                             'to verify valid values.')
1115         err = self.assertRaises(
1116             exception.UnknownFieldError,
1117             lambda: self._single_node_template_content_test(tpl_snippet))
1118         self.assertEqual(expectedmessage, err.__str__())
1119
1120     def test_relationship_template_properties(self):
1121         tpl_snippet = '''
1122         relationship_templates:
1123             storage_attachto:
1124                 type: AttachesTo
1125                 properties:
1126                   device: test_device
1127         '''
1128         expectedmessage = _('"properties" of template "storage_attachto" is '
1129                             'missing required field "[\'location\']".')
1130         rel_template = (toscaparser.utils.yamlparser.
1131                         simple_parse(tpl_snippet))['relationship_templates']
1132         name = list(rel_template.keys())[0]
1133         rel_template = RelationshipTemplate(rel_template[name], name)
1134         err = self.assertRaises(exception.MissingRequiredFieldError,
1135                                 rel_template.validate)
1136         self.assertEqual(expectedmessage, six.text_type(err))
1137
1138     def test_invalid_template_version(self):
1139         tosca_tpl = os.path.join(
1140             os.path.dirname(os.path.abspath(__file__)),
1141             "data/test_invalid_template_version.yaml")
1142         self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl)
1143         valid_versions = ', '.join(ToscaTemplate.VALID_TEMPLATE_VERSIONS)
1144         exception.ExceptionCollector.assertExceptionMessage(
1145             exception.InvalidTemplateVersion,
1146             (_('The template version "tosca_xyz" is invalid. Valid versions '
1147                'are "%s".') % valid_versions))
1148
1149     def test_node_template_capabilities_properties(self):
1150         # validating capability property values
1151         tpl_snippet = '''
1152         node_templates:
1153           server:
1154             type: tosca.nodes.WebServer
1155             capabilities:
1156               data_endpoint:
1157                 properties:
1158                   initiator: test
1159         '''
1160         expectedmessage = _('The value "test" of property "initiator" is '
1161                             'not valid. Expected a value from "[source, '
1162                             'target, peer]".')
1163
1164         err = self.assertRaises(
1165             exception.ValidationError,
1166             lambda: self._single_node_template_content_test(tpl_snippet))
1167         self.assertEqual(expectedmessage, err.__str__())
1168
1169         tpl_snippet = '''
1170         node_templates:
1171           server:
1172             type: tosca.nodes.Compute
1173             capabilities:
1174               host:
1175                 properties:
1176                   disk_size: 10 GB
1177                   num_cpus: { get_input: cpus }
1178                   mem_size: 4096 MB
1179               os:
1180                 properties:
1181                   architecture: x86_64
1182                   type: Linux
1183                   distribution: Fedora
1184                   version: 18.0
1185               scalable:
1186                 properties:
1187                   min_instances: 1
1188                   max_instances: 3
1189                   default_instances: 5
1190         '''
1191         expectedmessage = _('"properties" of template "server": '
1192                             '"default_instances" value is not between '
1193                             '"min_instances" and "max_instances".')
1194         err = self.assertRaises(
1195             exception.ValidationError,
1196             lambda: self._single_node_template_content_test(tpl_snippet))
1197         self.assertEqual(expectedmessage, err.__str__())
1198
1199     def test_node_template_objectstorage_without_required_property(self):
1200         tpl_snippet = '''
1201         node_templates:
1202           server:
1203             type: tosca.nodes.ObjectStorage
1204             properties:
1205               maxsize: 1 GB
1206         '''
1207         expectedmessage = _('"properties" of template "server" is missing '
1208                             'required field "[\'name\']".')
1209         err = self.assertRaises(
1210             exception.MissingRequiredFieldError,
1211             lambda: self._single_node_template_content_test(tpl_snippet))
1212         self.assertEqual(expectedmessage, err.__str__())
1213
1214     def test_node_template_objectstorage_with_invalid_scalar_unit(self):
1215         tpl_snippet = '''
1216         node_templates:
1217           server:
1218             type: tosca.nodes.ObjectStorage
1219             properties:
1220               name: test
1221               maxsize: -1
1222         '''
1223         expectedmessage = _('"-1" is not a valid scalar-unit.')
1224         err = self.assertRaises(
1225             ValueError,
1226             lambda: self._single_node_template_content_test(tpl_snippet))
1227         self.assertEqual(expectedmessage, err.__str__())
1228
1229     def test_node_template_objectstorage_with_invalid_scalar_type(self):
1230         tpl_snippet = '''
1231         node_templates:
1232           server:
1233             type: tosca.nodes.ObjectStorage
1234             properties:
1235               name: test
1236               maxsize: 1 XB
1237         '''
1238         expectedmessage = _('"1 XB" is not a valid scalar-unit.')
1239         err = self.assertRaises(
1240             ValueError,
1241             lambda: self._single_node_template_content_test(tpl_snippet))
1242         self.assertEqual(expectedmessage, err.__str__())
1243
1244     def test_special_keywords(self):
1245         """Test special keywords
1246
1247            Test that special keywords, e.g. metadata, which are not part
1248            of specification do not throw any validation error.
1249         """
1250         tpl_snippet_metadata_map = '''
1251         node_templates:
1252           server:
1253             type: tosca.nodes.Compute
1254             metadata:
1255               name: server A
1256               role: master
1257         '''
1258         self._single_node_template_content_test(tpl_snippet_metadata_map)
1259
1260         tpl_snippet_metadata_inline = '''
1261         node_templates:
1262           server:
1263             type: tosca.nodes.Compute
1264             metadata: none
1265         '''
1266         self._single_node_template_content_test(tpl_snippet_metadata_inline)
1267
1268     def test_policy_valid_keynames(self):
1269         tpl_snippet = '''
1270         policies:
1271           - servers_placement:
1272               type: tosca.policies.Placement
1273               description: Apply placement policy to servers
1274               metadata: { user1: 1001, user2: 1002 }
1275               targets: [ serv1, serv2 ]
1276         '''
1277         policies = (toscaparser.utils.yamlparser.
1278                     simple_parse(tpl_snippet))['policies'][0]
1279         name = list(policies.keys())[0]
1280         Policy(name, policies[name], None, None)
1281
1282     def test_policy_invalid_keyname(self):
1283         tpl_snippet = '''
1284         policies:
1285           - servers_placement:
1286               type: tosca.policies.Placement
1287               testkey: testvalue
1288         '''
1289         policies = (toscaparser.utils.yamlparser.
1290                     simple_parse(tpl_snippet))['policies'][0]
1291         name = list(policies.keys())[0]
1292
1293         expectedmessage = _('Policy "servers_placement" contains '
1294                             'unknown field "testkey". Refer to the '
1295                             'definition to verify valid values.')
1296         err = self.assertRaises(
1297             exception.UnknownFieldError,
1298             lambda: Policy(name, policies[name], None, None))
1299         self.assertEqual(expectedmessage, err.__str__())
1300
1301     def test_policy_trigger_valid_keyname(self):
1302         tpl_snippet = '''
1303         triggers:
1304          - resize_compute:
1305              description: trigger
1306              event_type: tosca.events.resource.utilization
1307              schedule:
1308                start_time: "2015-05-07T07:00:00Z"
1309                end_time: "2015-06-07T07:00:00Z"
1310              target_filter:
1311                node: master-container
1312                requirement: host
1313                capability: Container
1314              condition:
1315                constraint: utilization greater_than 50%
1316                period: 60
1317                evaluations: 1
1318                method : average
1319              action:
1320                resize: # Operation name
1321                 inputs:
1322                  strategy: LEAST_USED
1323                  implementation: Senlin.webhook()
1324         '''
1325         triggers = (toscaparser.utils.yamlparser.
1326                     simple_parse(tpl_snippet))['triggers'][0]
1327         name = list(triggers.keys())[0]
1328         Triggers(name, triggers[name])
1329
1330     def test_policy_trigger_invalid_keyname(self):
1331         tpl_snippet = '''
1332         triggers:
1333          - resize_compute:
1334              description: trigger
1335              event_type: tosca.events.resource.utilization
1336              schedule:
1337                start_time: "2015-05-07T07:00:00Z"
1338                end_time: "2015-06-07T07:00:00Z"
1339              target_filter1:
1340                node: master-container
1341                requirement: host
1342                capability: Container
1343              condition:
1344                constraint: utilization greater_than 50%
1345                period1: 60
1346                evaluations: 1
1347                method: average
1348              action:
1349                resize: # Operation name
1350                 inputs:
1351                  strategy: LEAST_USED
1352                  implementation: Senlin.webhook()
1353         '''
1354         triggers = (toscaparser.utils.yamlparser.
1355                     simple_parse(tpl_snippet))['triggers'][0]
1356         name = list(triggers.keys())[0]
1357         expectedmessage = _(
1358             'Triggers "resize_compute" contains unknown field '
1359             '"target_filter1". Refer to the definition '
1360             'to verify valid values.')
1361         err = self.assertRaises(
1362             exception.UnknownFieldError,
1363             lambda: Triggers(name, triggers[name]))
1364         self.assertEqual(expectedmessage, err.__str__())
1365
1366     def test_policy_missing_required_keyname(self):
1367         tpl_snippet = '''
1368         policies:
1369           - servers_placement:
1370               description: test description
1371         '''
1372         policies = (toscaparser.utils.yamlparser.
1373                     simple_parse(tpl_snippet))['policies'][0]
1374         name = list(policies.keys())[0]
1375
1376         expectedmessage = _('Template "servers_placement" is missing '
1377                             'required field "type".')
1378         err = self.assertRaises(
1379             exception.MissingRequiredFieldError,
1380             lambda: Policy(name, policies[name], None, None))
1381         self.assertEqual(expectedmessage, err.__str__())