Fix Apexlake Documentation
[yardstick.git] / yardstick / vTC / apexlake / tests / common_test.py
1 # Copyright (c) 2015 Intel Research and Development Ireland Ltd.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import unittest
16 import mock
17 import os
18 import logging
19 import ConfigParser
20 import experimental_framework.common as common
21 import experimental_framework.constants.conf_file_sections as cf
22
23 __author__ = 'vmricco'
24
25
26 def reset_common():
27     common.LOG = None
28     common.CONF_FILE = None
29     common.DEPLOYMENT_UNIT = None
30     common.ITERATIONS = None
31     common.BASE_DIR = None
32     common.RESULT_DIR = None
33     common.TEMPLATE_DIR = None
34     common.TEMPLATE_NAME = None
35     common.TEMPLATE_FILE_EXTENSION = None
36     common.PKTGEN = None
37     common.PKTGEN_DIR = None
38     common.PKTGEN_DPDK_DIRECTORY = None
39     common.PKTGEN_PROGRAM = None
40     common.PKTGEN_COREMASK = None
41     common.PKTGEN_MEMCHANNEL = None
42     common.PKTGEN_BUS_SLOT_NIC_1 = None
43     common.PKTGEN_BUS_SLOT_NIC_2 = None
44     common.INFLUXDB_IP = None
45     common.INFLUXDB_PORT = None
46     common.INFLUXDB_DB_NAME = None
47
48
49 class DummyConfigurationFile(common.ConfigurationFile):
50     def __init__(self, sections, conf_file=''):
51         pass
52
53     def get_variable(self, section, variable_name):
54         return 'vTC.yaml'
55
56     def get_variable_list(self, section):
57         return ['template_base_name']
58
59
60 class DummyConfigurationFile2(common.ConfigurationFile):
61     def __init__(self, sections):
62         self.pktgen_counter = 0
63
64     def get_variable(self, section, variable_name):
65         if variable_name == cf.CFSG_TEMPLATE_NAME:
66             return 'vTC.yaml'
67         if variable_name == cf.CFSG_ITERATIONS:
68             return '2'
69         if variable_name == cf.CFSG_DEBUG:
70             return 'True'
71         if variable_name == cf.CFSP_PACKET_GENERATOR:
72             if self.pktgen_counter == 1:
73                 return 'non_supported'
74             self.pktgen_counter += 1
75             return 'dpdk_pktgen'
76         if variable_name == cf.CFSP_DPDK_PKTGEN_DIRECTORY:
77             return os.getcwd()
78         if variable_name == cf.CFSP_DPDK_PROGRAM_NAME:
79             return 'program'
80         if variable_name == cf.CFSP_DPDK_COREMASK:
81             return 'coremask'
82         if variable_name == cf.CFSP_DPDK_MEMORY_CHANNEL:
83             return 'memchannel'
84         if variable_name == cf.CFSP_DPDK_BUS_SLOT_NIC_1:
85             return 'bus_slot_nic_1'
86         if variable_name == cf.CFSP_DPDK_BUS_SLOT_NIC_2:
87             return 'bus_slot_nic_2'
88         if variable_name == cf.CFSP_DPDK_DPDK_DIRECTORY:
89             return os.getcwd()
90
91     def get_variable_list(self, section):
92         if section == cf.CFS_PKTGEN:
93             return [
94                 cf.CFSP_DPDK_NAME_IF_2,
95                 cf.CFSP_DPDK_NAME_IF_1,
96                 cf.CFSP_DPDK_BUS_SLOT_NIC_1,
97                 cf.CFSP_DPDK_BUS_SLOT_NIC_2,
98                 cf.CFSP_DPDK_COREMASK,
99                 cf.CFSP_DPDK_DPDK_DIRECTORY,
100                 cf.CFSP_DPDK_PKTGEN_DIRECTORY,
101                 cf.CFSP_DPDK_MEMORY_CHANNEL,
102                 cf.CFSP_DPDK_PROGRAM_NAME,
103                 cf.CFSP_PACKET_GENERATOR
104             ]
105         else:
106             return [
107                 'template_base_name',
108                 'iterations',
109                 cf.CFSG_DEBUG
110             ]
111
112
113 class TestCommonInit(unittest.TestCase):
114
115     def setUp(self):
116         common.CONF_FILE = DummyConfigurationFile('')
117         self.dir = '{}/{}'.format(os.getcwd(),
118                                   'experimental_framework/')
119
120     def tearDown(self):
121         reset_common()
122         # common.CONF_FILE = None
123
124     @mock.patch('os.getcwd')
125     @mock.patch('experimental_framework.common.init_conf_file')
126     @mock.patch('experimental_framework.common.init_general_vars')
127     @mock.patch('experimental_framework.common.init_log')
128     @mock.patch('experimental_framework.common.init_pktgen')
129     @mock.patch('experimental_framework.common.CONF_FILE')
130     def test_init_for_success(self, mock_conf_file, init_pkgen, init_log,
131                               init_general_vars, init_conf_file, mock_getcwd):
132         mock_getcwd.return_value = self.dir
133         common.init(True)
134         init_pkgen.assert_called_once()
135         init_conf_file.assert_called_once()
136         init_general_vars.assert_called_once()
137         init_log.assert_called_once()
138         expected = self.dir.split('experimental_framework/')[0]
139         self.assertEqual(common.BASE_DIR, expected)
140
141     @mock.patch('experimental_framework.common.InputValidation')
142     @mock.patch('os.path.exists')
143     @mock.patch('os.makedirs')
144     @mock.patch('experimental_framework.common.LOG')
145     def test_init_general_vars_for_success(self, mock_log, mock_makedirs,
146                                            mock_path_exists, mock_val_file):
147         common.BASE_DIR = "{}/".format(os.getcwd())
148         mock_path_exists.return_value = False
149         mock_val_file.return_value = True
150         common.init_general_vars()
151         self.assertEqual(common.TEMPLATE_FILE_EXTENSION, '.yaml')
152         self.assertEqual(common.TEMPLATE_DIR, '/tmp/apexlake/heat_templates/')
153         self.assertEqual(common.TEMPLATE_NAME, 'vTC.yaml')
154         self.assertEqual(common.RESULT_DIR, '/tmp/apexlake/results/')
155         self.assertEqual(common.ITERATIONS, 1)
156     # mock_makedirs.assert_called_once_with('/tmp/apexlake/heat_templates/')
157
158
159 class TestCommonInit2(unittest.TestCase):
160
161     def setUp(self):
162         common.CONF_FILE = DummyConfigurationFile2('')
163         self.dir = '{}/{}'.format(os.getcwd(), 'experimental_framework/')
164
165     def tearDown(self):
166         reset_common()
167         common.CONF_FILE = None
168
169     @mock.patch('experimental_framework.common.LOG')
170     def test_init_general_vars_2_for_success(self, mock_log):
171         common.BASE_DIR = "{}/".format(os.getcwd())
172         common.init_general_vars()
173         self.assertEqual(common.TEMPLATE_FILE_EXTENSION, '.yaml')
174         self.assertEqual(common.TEMPLATE_DIR, '/tmp/apexlake/heat_templates/')
175         self.assertEqual(common.TEMPLATE_NAME, 'vTC.yaml')
176         self.assertEqual(common.RESULT_DIR, '/tmp/apexlake/results/')
177         self.assertEqual(common.ITERATIONS, 2)
178
179     def test_init_log_2_for_success(self):
180         common.init_log()
181         self.assertIsInstance(common.LOG, logging.RootLogger)
182
183     def test_init_pktgen_for_success(self):
184         common.init_pktgen()
185         self.assertEqual(common.PKTGEN, 'dpdk_pktgen')
186         directory = self.dir.split('experimental_framework/')[0]
187         self.assertEqual(common.PKTGEN_DIR, directory)
188         self.assertEqual(common.PKTGEN_PROGRAM, 'program')
189         self.assertEqual(common.PKTGEN_COREMASK, 'coremask')
190         self.assertEqual(common.PKTGEN_MEMCHANNEL, 'memchannel')
191         self.assertEqual(common.PKTGEN_BUS_SLOT_NIC_1, 'bus_slot_nic_1')
192         self.assertEqual(common.PKTGEN_BUS_SLOT_NIC_2, 'bus_slot_nic_2')
193         expected_dir = "{}/".format(os.getcwd())
194         self.assertEqual(common.PKTGEN_DPDK_DIRECTORY, expected_dir)
195
196     def test_init_pktgen_for_failure(self):
197         common.CONF_FILE.get_variable('', cf.CFSP_PACKET_GENERATOR)
198         self.assertRaises(ValueError, common.init_pktgen)
199
200
201 class TestConfFileInitialization(unittest.TestCase):
202
203     def setUp(self):
204         pass
205
206     def tearDown(self):
207         reset_common()
208
209     @mock.patch('experimental_framework.common.ConfigurationFile',
210                 side_effect=DummyConfigurationFile)
211     def test_init_conf_file_for_success(self, conf_file):
212         common.CONF_FILE = None
213         common.init_conf_file(False)
214         self.assertIsInstance(common.CONF_FILE,
215                               DummyConfigurationFile)
216
217         common.CONF_FILE = None
218         common.init_conf_file(True)
219         self.assertIsInstance(common.CONF_FILE,
220                               DummyConfigurationFile)
221
222     @mock.patch('experimental_framework.common.CONF_FILE')
223     def test_init_log_for_success(self, mock_conf_file):
224         mock_conf_file.get_variable_list.return_value = 'value'
225         common.init_log()
226         self.assertIsInstance(common.LOG, logging.RootLogger)
227
228     @mock.patch('experimental_framework.common.CONF_FILE')
229     def test_init_influxdb_for_success(self, mock_conf_file):
230         mock_conf_file.get_variable.return_value = 'value'
231         common.init_influxdb()
232         self.assertEqual(common.INFLUXDB_IP, 'value')
233         self.assertEqual(common.INFLUXDB_PORT, 'value')
234         self.assertEqual(common.INFLUXDB_DB_NAME, 'value')
235
236
237 class DummyConfigurationFile3(common.ConfigurationFile):
238     counter = 0
239
240     def __init__(self, sections, config_file='conf.cfg'):
241         common.ConfigurationFile.__init__(self, sections, config_file)
242
243     @staticmethod
244     def _config_section_map(section, config_file, get_counter=None):
245         if get_counter:
246             return DummyConfigurationFile3.counter
247         else:
248             DummyConfigurationFile3.counter += 1
249             return dict()
250
251
252 class TestConfigFileClass(unittest.TestCase):
253
254     def setUp(self):
255         self.sections = [
256             'General',
257             'OpenStack',
258             'Experiment-VNF',
259             'PacketGen',
260             'Deployment-parameters',
261             'Testcase-parameters'
262         ]
263         c_file = './tests/data/common/conf.cfg'
264         common.BASE_DIR = os.getcwd()
265         self.conf_file = common.ConfigurationFile(self.sections, c_file)
266
267     def tearDown(self):
268         reset_common()
269         common.BASE_DIR = None
270
271     @mock.patch('experimental_framework.common.ConfigurationFile.'
272                 '_config_section_map',
273                 side_effect=DummyConfigurationFile3._config_section_map)
274     def test___init___for_success(self, mock_conf_map):
275         sections = ['General', 'OpenStack', 'Experiment-VNF', 'PacketGen',
276                     'Deployment-parameters', 'Testcase-parameters']
277         c = DummyConfigurationFile3(
278             sections, config_file='./tests/data/common/conf.cfg')
279         self.assertEqual(
280             DummyConfigurationFile3._config_section_map('', '', True),
281             6)
282         for section in sections:
283             self.assertEqual(getattr(c, section), dict())
284
285     def test__config_section_map_for_success(self):
286         general_section = 'General'
287         # openstack_section = 'OpenStack'
288         config_file = 'tests/data/common/conf.cfg'
289         config = ConfigParser.ConfigParser()
290         config.read(config_file)
291
292         expected = {
293             'benchmarks': 'b_marks',
294             'iterations': '1',
295             'template_base_name': 't_name'
296         }
297         output = common.\
298             ConfigurationFile._config_section_map(general_section, config)
299         self.assertEqual(expected, output)
300
301     @mock.patch('experimental_framework.common.'
302                 'ConfigurationFile.get_variable_list')
303     def test_get_variable_for_success(self, mock_get_var_list):
304         section = self.sections[0]
305         variable_name = 'template_base_name'
306         expected = 't_name'
307         mock_get_var_list.return_value = [variable_name]
308         output = self.conf_file.get_variable(section, variable_name)
309         self.assertEqual(expected, output)
310
311     @mock.patch('experimental_framework.common.'
312                 'ConfigurationFile.get_variable_list')
313     def test_get_variable_for_failure(self, mock_get_var_list):
314         section = self.sections[0]
315         variable_name = 'something_else'
316         self.assertRaises(
317             ValueError,
318             self.conf_file.get_variable,
319             section, variable_name
320         )
321
322     def test_get_variable_list_for_success(self):
323         section = self.sections[0]
324         expected = {
325             'benchmarks': 'b_marks',
326             'iterations': '1',
327             'template_base_name': 't_name'
328         }
329         output = self.conf_file.get_variable_list(section)
330         self.assertEqual(expected, output)
331
332     def test_get_variable_list_for_failure(self):
333         section = 'something_else'
334         self.assertRaises(
335             ValueError,
336             self.conf_file.get_variable_list,
337             section)
338
339
340 class DummyConfigurationFile4(common.ConfigurationFile):
341
342     def get_variable(self, section, variable_name):
343         if variable_name == 'vnic2_type':
344             return '"value"'
345         elif variable_name == cf.CFSG_BENCHMARKS:
346             return "BenchmarkClass1, BenchmarkClass2"
347         return '@string "value"'
348
349     # def get_variable_list(self, section):
350     #     return list()
351
352
353 class TestCommonMethods(unittest.TestCase):
354
355     def setUp(self):
356         self.sections = [
357             'General',
358             'OpenStack',
359             'Experiment-VNF',
360             'PacketGen',
361             'Deployment-parameters',
362             'Testcase-parameters'
363         ]
364         config_file = './tests/data/common/conf.cfg'
365         common.BASE_DIR = os.getcwd()
366         common.CONF_FILE = DummyConfigurationFile4(self.sections, config_file)
367
368     def tearDown(self):
369         reset_common()
370         common.CONF_FILE = None
371
372     def test_get_credentials_for_success(self):
373         expected = {
374             'ip_controller': '@string "value"',
375             'project': '@string "value"',
376             'auth_uri': '@string "value"',
377             'user': '@string "value"',
378             'heat_url': '@string "value"',
379             'password': '@string "value"'
380         }
381         output = common.get_credentials()
382         self.assertEqual(expected, output)
383
384     def test_get_heat_template_params_for_success(self):
385         expected = {
386             'param_1': '@string "value"',
387             'param_2': '@string "value"',
388             'param_3': '@string "value"',
389             'param_4': '@string "value"'
390         }
391         output = common.get_heat_template_params()
392         self.assertEqual(expected, output)
393
394     def test_get_testcase_params_for_success(self):
395         expected = {'test_case_param': '@string "value"'}
396         output = common.get_testcase_params()
397         self.assertEqual(expected, output)
398
399     def test_get_file_first_line_for_success(self):
400         file = 'tests/data/common/conf.cfg'
401         expected = '[General]\n'
402         output = common.get_file_first_line(file)
403         self.assertEqual(expected, output)
404
405     def test_replace_in_file_for_success(self):
406         filename = 'tests/data/common/file_replacement.txt'
407         text_to_search = 'replacement of'
408         text_to_replace = '***'
409         common.replace_in_file(filename, text_to_search, text_to_replace)
410         after = open(filename, 'r').readline()
411         self.assertEqual(after, 'Test for the *** strings into a file\n')
412         text_to_search = '***'
413         text_to_replace = 'replacement of'
414         common.replace_in_file(filename, text_to_search, text_to_replace)
415
416     @mock.patch('os.system')
417     @mock.patch('experimental_framework.common.LOG')
418     def test_run_command_for_success(self, mock_log, mock_os_system):
419         command = 'command to be run'
420         common.run_command(command)
421         mock_os_system.assert_called_once_with(command)
422
423     @mock.patch('experimental_framework.common.run_command')
424     def test_push_data_influxdb_for_success(self, mock_run_cmd):
425         data = 'string that describes the data'
426         expected = "curl -i -XPOST 'http://None:None/write?db=None' " \
427                    "--data-binary string that describes the data"
428         common.push_data_influxdb(data)
429         mock_run_cmd.assert_called_once_with(expected)
430
431     def test_get_base_dir_for_success(self):
432         base_dir = common.BASE_DIR
433         common.BASE_DIR = 'base_dir'
434         expected = 'base_dir'
435         output = common.get_base_dir()
436         self.assertEqual(expected, output)
437         common.BASE_DIR = base_dir
438
439     def test_get_template_dir_for_success(self):
440         template_dir = common.TEMPLATE_DIR
441         common.TEMPLATE_DIR = 'base_dir'
442         expected = 'base_dir'
443         output = common.get_template_dir()
444         self.assertEqual(expected, output)
445         common.TEMPLATE_DIR = template_dir
446
447     def test_get_dpdk_pktgen_vars_test(self):
448         # Test 1
449         common.PKTGEN = 'dpdk_pktgen'
450         common.PKTGEN_DIR = 'var'
451         common.PKTGEN_PROGRAM = 'var'
452         common.PKTGEN_COREMASK = 'var'
453         common.PKTGEN_MEMCHANNEL = 'var'
454         common.PKTGEN_BUS_SLOT_NIC_1 = 'var'
455         common.PKTGEN_BUS_SLOT_NIC_2 = 'var'
456         common.PKTGEN_NAME_NIC_1 = 'var'
457         common.PKTGEN_NAME_NIC_2 = 'var'
458         common.PKTGEN_DPDK_DIRECTORY = 'var'
459         expected = {
460             'bus_slot_nic_1': 'var',
461             'bus_slot_nic_2': 'var',
462             'name_if_1': 'var',
463             'name_if_2': 'var',
464             'coremask': 'var',
465             'dpdk_directory': 'var',
466             'memory_channels': 'var',
467             'pktgen_directory': 'var',
468             'program_name': 'var'
469         }
470         output = common.get_dpdk_pktgen_vars()
471         self.assertEqual(expected, output)
472
473         # Test 2
474         common.PKTGEN = 'something_else'
475         common.PKTGEN_DIR = 'var'
476         common.PKTGEN_PROGRAM = 'var'
477         common.PKTGEN_COREMASK = 'var'
478         common.PKTGEN_MEMCHANNEL = 'var'
479         common.PKTGEN_BUS_SLOT_NIC_1 = 'var'
480         common.PKTGEN_BUS_SLOT_NIC_2 = 'var'
481         common.PKTGEN_DPDK_DIRECTORY = 'var'
482         expected = {}
483         output = common.get_dpdk_pktgen_vars()
484         self.assertEqual(expected, output)
485
486     @mock.patch('experimental_framework.common.LOG')
487     def test_get_deployment_configuration_variables_for_success(self,
488                                                                 mock_log):
489         expected = {
490             'vcpu': ['value'],
491             'vnic1_type': ['value'],
492             'ram': ['value'],
493             'vnic2_type': ['value']
494         }
495         output = common.get_deployment_configuration_variables_from_conf_file()
496         self.assertEqual(expected, output)
497
498     def test_get_benchmarks_from_conf_file_for_success(self):
499         expected = ['BenchmarkClass1', 'BenchmarkClass2']
500         output = common.get_benchmarks_from_conf_file()
501         self.assertEqual(expected, output)
502
503
504 class TestinputValidation(unittest.TestCase):
505
506     def setUp(self):
507         pass
508
509     def tearDown(self):
510         reset_common()
511
512     def test_validate_string_for_success(self):
513         output = common.InputValidation.validate_string('string', '')
514         self.assertTrue(output)
515
516     def test_validate_string_for_failure(self):
517         self.assertRaises(
518             ValueError,
519             common.InputValidation.validate_string,
520             list(), ''
521         )
522
523     def test_validate_int_for_success(self):
524         output = common.InputValidation.validate_integer(1111, '')
525         self.assertTrue(output)
526
527     def test_validate_int_for_failure(self):
528         self.assertRaises(
529             ValueError,
530             common.InputValidation.validate_integer,
531             list(), ''
532         )
533
534     def test_validate_dict_for_success(self):
535         output = common.InputValidation.validate_dictionary(dict(), '')
536         self.assertTrue(output)
537
538     def test_validate_dict_for_failure(self):
539         self.assertRaises(
540             ValueError,
541             common.InputValidation.validate_dictionary,
542             list(), ''
543         )
544
545     def test_validate_file_exist_for_success(self):
546         filename = 'tests/data/common/file_replacement.txt'
547         output = common.InputValidation.validate_file_exist(filename, '')
548         self.assertTrue(output)
549
550     def test_validate_file_exist_for_failure(self):
551         filename = 'tests/data/common/file_replacement'
552         self.assertRaises(
553             ValueError,
554             common.InputValidation.validate_file_exist,
555             filename, ''
556         )
557
558     def test_validate_directory_exist_and_format_for_success(self):
559         directory = 'tests/data/common/'
560         output = common.InputValidation.\
561             validate_directory_exist_and_format(directory, '')
562         self.assertTrue(output)
563
564     def test_validate_directory_exist_and_format_for_failure(self):
565         directory = 'tests/data/com/'
566         self.assertRaises(
567             ValueError,
568             common.InputValidation.validate_directory_exist_and_format,
569             directory, ''
570         )
571
572     @mock.patch('experimental_framework.common.CONF_FILE')
573     def test_validate_configuration_file_parameter_for_success(self,
574                                                                mock_conf):
575         mock_conf.get_variable_list.return_value = ['param']
576         section = ''
577         parameter = 'param'
578         message = ''
579         output = common.InputValidation.\
580             validate_configuration_file_parameter(section, parameter, message)
581         self.assertTrue(output)
582
583     @mock.patch('experimental_framework.common.CONF_FILE')
584     def test_validate_configuration_file_parameter_for_failure(
585             self, mock_conf_file):
586         section = ''
587         parameter = 'something_else'
588         message = ''
589         mock_conf_file.get_variable_list.return_value(['parameter'])
590         self.assertRaises(
591             ValueError,
592             common.InputValidation.
593             validate_configuration_file_parameter,
594             section, parameter, message
595         )
596
597     def test_validate_configuration_file_section_for_success(self):
598         section = 'General'
599         message = ''
600         output = common.InputValidation.\
601             validate_configuration_file_section(section, message)
602         self.assertTrue(output)
603
604     def test_validate_configuration_file_section_for_failure(self):
605         section = 'Something-Else'
606         message = ''
607         self.assertRaises(
608             ValueError,
609             common.InputValidation.validate_configuration_file_section,
610             section, message
611         )
612
613     def test_validate_boolean_for_success(self):
614         message = ''
615         boolean = True
616         output = common.InputValidation.validate_boolean(boolean, message)
617         self.assertTrue(output)
618
619         boolean = 'True'
620         output = common.InputValidation.validate_boolean(boolean, message)
621         self.assertTrue(output)
622
623         boolean = 'False'
624         output = common.InputValidation.validate_boolean(boolean, message)
625         self.assertFalse(output)
626
627     def test_validate_boolean_for_failure(self):
628         message = ''
629         boolean = 'string'
630         self.assertRaises(
631             ValueError,
632             common.InputValidation.validate_boolean,
633             boolean, message
634         )
635
636     def test_validate_os_credentials_for_failure(self):
637         # Test 1
638         credentials = list()
639         self.assertRaises(ValueError,
640                           common.InputValidation.validate_os_credentials,
641                           credentials)
642
643         # Test 2
644         credentials = dict()
645         credentials['ip_controller'] = ''
646         credentials['heat_url'] = ''
647         credentials['user'] = ''
648         credentials['password'] = ''
649         credentials['auth_uri'] = ''
650         # credentials['project'] = ''
651         self.assertRaises(ValueError,
652                           common.InputValidation.validate_os_credentials,
653                           credentials)
654
655     def test_validate_os_credentials_for_success(self):
656         credentials = dict()
657         credentials['ip_controller'] = ''
658         credentials['heat_url'] = ''
659         credentials['user'] = ''
660         credentials['password'] = ''
661         credentials['auth_uri'] = ''
662         credentials['project'] = ''
663         self.assertTrue(
664             common.InputValidation.validate_os_credentials(credentials))