2 # Copyright 2019 Huawei Technologies Co., Ltd.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # This script uses the ONAP CLI for providing the end-end service creation and termination.
17 # Used in devops, testing, certification and production
18 # NOTE: This feature is avaialble as ONAP CLI vnf-tosca-lcm
20 # Author: kanagaraj.manickam@huawei.com
34 from argparse import RawTextHelpFormatter
36 if platform.system() == 'Windows':
37 CMD_NAME = 'oclip.cmd'
41 class OcompException(Exception):
42 def __init__(self, code, message):
43 super(OcompException, self).__init__()
45 self.message = message;
49 request_id = os.environ.get('OPEN_CLI_REQUEST_ID'),
52 product = os.environ.get('OPEN_CLI_PRODUCT_IN_USE'),
53 profile = os.environ.get('OPEN_CLI_PROFILE')):
55 request_id = str(uuid.uuid4())
56 self.request_id = request_id
59 self.product = product
60 self.profile = profile
63 return str(vars(self))
70 return os.popen('{} --version'.format(CMD_NAME)).read()
72 def run(self, command, params={}, product=None, profile=None, request_id=None):
76 request_id = self.request_id
79 CMD.append('--request-id')
80 CMD.append(request_id)
83 product = self.product
86 CMD.append('--product')
90 profile = self.profile
93 CMD.append('--profile')
101 CMD.append('--format')
102 CMD.append(self.format)
104 for name, value in params.items():
105 CMD.append('--{}'.format(name))
108 cmd_string = ' '.join(CMD)
112 res = subprocess.Popen(CMD, stdout=subprocess.PIPE)
114 result = res.stdout.read().strip()
115 print (res.returncode, result)
117 if res.returncode != 0:# and res.returncode != 1:
118 raise OcompException(9999, result)
120 return json.loads(result)
124 sys.stderr.write(str(e))
125 msg = 'failed to executed the command {}'.format(cmd_string)
127 raise OcompException(9999, msg)
136 self.conf = conf or {}
137 self.ocomp = OCOMP(request_id, debug, product=product, profile=profile)
139 self.tag = 'Powered by Open Command Platform - OCOMP'
142 if self.conf['ONAP']:
143 for attr in self.conf['ONAP']:
144 setattr(self, attr, self.conf['ONAP'][attr])
146 self.conf['ONAP'] = {}
148 def create_vlm(self):
151 if not self.vlm_id and not self.vlm_version:
152 output = self.ocomp.run(command='vlm-create',
153 params={'vendor-name': self.conf['vnf']['vendor-name'],
154 'description': self.tag})
156 self.vlm_id = output['id']
157 self.vlm_version = output['version']
160 if not self.entitlement_id:
161 output = self.ocomp.run(command='vlm-entitlement-pool-create',
162 params={'name': '{} Entitlement Pool'.format(self.conf['vnf']['vendor-name']),
163 'vlm-id': self.vlm_id,
164 'vlm-version': self.vlm_version,
165 'manufacture-reference-number': 'OCOMP',
166 'start-date': datetime.datetime.strftime(datetime.datetime.today(),'%m/%d/%Y'),
167 'expiry-date': datetime.datetime.strftime(datetime.datetime.today() + datetime.timedelta(1),'%m/%d/%Y')})
168 self.entitlement_id = output['id']
171 if not self.key_group_id:
172 output = self.ocomp.run(command='vlm-key-group-create',
173 params={'name': '{} Key goroup'.format(self.conf['vnf']['vendor-name']),
174 'vlm-id': self.vlm_id,
175 'vlm-version': self.vlm_version,
176 'type': 'Universal'})
178 self.key_group_id = output['id']
181 if not self.feature_group_id:
182 output = self.ocomp.run(command='vlm-feature-group-create',
183 params={'name': '{} Feature group'.format(self.conf['vnf']['vendor-name']),
184 'vlm-id': self.vlm_id,
185 'vlm-version': self.vlm_version,
186 'vlm-key-group-id': self.key_group_id,
187 'vlm-entitle-pool-id': self.entitlement_id,
188 'part-number': '100000'})
190 self.feature_group_id = output['id']
193 if not self.agreement_id:
194 output = self.ocomp.run(command='vlm-aggreement-create',
195 params={'name': '{} Agreement'.format(self.conf['vnf']['vendor-name']),
196 'vlm-id': self.vlm_id,
197 'vlm-version': self.vlm_version,
198 'vlm-feature-group-id': self.feature_group_id})
200 self.agreement_id = output['id']
204 self.ocomp.run(command='vlm-submit',
205 params={'vlm-id': self.vlm_id,
206 'vlm-version': self.vlm_version})
208 def create_vsp(self):
209 if not self.vsp_id and not self.vsp_version and not self.vsp_version_id:
210 output = self.ocomp.run(command='vsp-create',
211 params={'vlm-id': self.vlm_id,
212 'vlm-version': self.vlm_version,
213 'vlm-vendor': self.conf['vnf']['vendor-name'],
214 'vsp-name': self.conf['vnf']['name'],
215 'vsp-description': self.tag,
216 'vlm-agreement-id': self.agreement_id,
217 'vlm-feature-group-id': self.feature_group_id})
218 self.vsp_id = output['id']
219 self.vsp_version_id = output['version-id']
220 self.vsp_version = output['version']
222 self.ocomp.run(command='vsp-add-artifact',
223 params={'vsp-id': self.vsp_id,
224 'vsp-version': self.vsp_version_id,
225 'vsp-file': self.conf['vnf']['vsp-csar']})
227 output = self.ocomp.run(command='vsp-validate',
228 params={'vsp-id': self.vsp_id,
229 'vsp-version': self.vsp_version_id})
230 if not output['status'] == "Success":
231 raise Exception("Invalid VSP package, please check it compliance using VTP")
233 self.ocomp.run(command='vsp-commit',
234 params={'vsp-id': self.vsp_id,
235 'vsp-version': self.vsp_version_id,
236 'remarks': self.tag})
238 self.ocomp.run(command='vsp-submit',
239 params={'vsp-id': self.vsp_id,
240 'vsp-version': self.vsp_version_id})
242 self.ocomp.run(command='vsp-package',
243 params={'vsp-id': self.vsp_id,
244 'vsp-version': self.vsp_version_id})
246 def create_vf_model(self):
247 if not self.vf_id and not self.vf_version:
248 output = self.ocomp.run(command='vf-model-create',
249 params={'name': '{} Vnf'.format(self.conf['vnf']['name']),
250 'vendor-name': self.conf['vnf']['vendor-name'],
251 # 'vsp-id': self.vsp_id,
252 # 'vsp-version': self.vsp_version, # TODO: SDC fails to add VSP, check it
253 'description': self.tag})
256 inputs = output['inputs'].replace('[', '').replace(']', '').split(',')
258 self.ocomp.run(command='vf-model-add-artifact',
259 params={'vf-id': vf_id,
261 'artifact': self.conf['vnf']['vnf-csar'],
262 'artifact-name': 'tosca csar'})
264 output = self.ocomp.run(command='vf-model-certify',
265 params={'vf-id': vf_id,
266 'remarks': self.tag})
267 self.vf_id = output['id']
268 self.vf_version = output['version']
269 self.vf_uuid = output['uuid']
270 self.vf_inputs = inputs
272 def create_service_model(self):
273 if not self.ns_id and not self.ns_version:
274 output = self.ocomp.run(command='service-model-create',
275 params={'service-name': '{} Service'.format(self.conf['vnf']['name']),
276 'description': self.tag,
277 'project-code': 'OCOMP',
278 'category': 'network l1-3',
279 'category-display-name': 'Network L1-3',
280 'icon-id': 'network_l_1-3'})
283 if not self.ns_vf_resource_id:
284 output = self.ocomp.run(command='service-model-add-vf',
285 params={'service-id': ns_id,
287 'vf-version': self.vf_version,
288 'vf-name': self.conf['vnf']['name']})
289 self.ns_vf_resource_id = output['id']
292 self.ocomp.run(command='service-model-add-artifact',
293 params={'service-id': ns_id,
295 'artifact': self.conf['vnf']['ns-csar'],
296 'artifact-name': 'tosca csar'})
297 #set property vnfmdriver
298 for input in self.vf_inputs:
299 if input.endswith('.nf_type'):
300 tkns = input.strip().split('.')
302 self.ocomp.run(command='service-model-set-property',
303 params={'service-id': ns_id,
305 'vf-resource-id': self.ns_vf_resource_id,
306 'property-name': 'nf_type',
307 'property-value': self.conf['vnf']['vnfm-driver'],
308 'input-uuid': input_uuid})
311 self.ocomp.run(command='service-model-test-request',
312 params={'service-id': ns_id,
313 'remarks': self.tag})
315 self.ocomp.run(command='service-model-test-start',
316 params={'service-id': ns_id})
318 output = self.ocomp.run(command='service-model-test-accept',
319 params={'service-id': ns_id,
320 'remarks': self.tag})
321 self.ns_id = output['id']
322 self.ns_version = output['version']
323 self.ns_uuid = output['uuid']
325 self.ocomp.run(command='service-model-approve',
326 params={'service-id': self.ns_id,
327 'remarks': self.tag})
329 self.ocomp.run(command='service-model-distribute',
330 params={'service-id': self.ns_id})
332 def setup_cloud_and_subscription(self):
334 if not self.location_id and not self.location_version:
335 location_id = 'ocomp-region-{}'.format(self.conf['ONAP']['uid'])
336 self.ocomp.run(command='complex-create',
337 params={'physical-location-id': location_id,
338 'data-center-code': 'ocomp',
339 'complex-name': location_id,
340 'identity-url': self.conf['cloud']['identity-url'],
341 'physical-location-type': 'phy_type',
342 'street1': 'ocomp-street1',
343 'street2': 'ocomp-street2',
344 'city': 'ocomp-city',
345 'state': 'ocomp-state',
346 'postal-code': '001481',
351 'elevation': 'ocomp-elelation',
352 'lata': 'ocomp-lata'})
353 self.location_id = location_id
356 output = self.ocomp.run(command='complex-list')
358 for location in output:
359 if location['complex-name'] == self.location_id:
360 self.location_version = location['resource-version']
363 if not self.cloud_id and not self.cloud_version:
364 cloud_id = 'OCOMP-{}'.format(self.conf['ONAP']['uid'])
365 self.ocomp.run(command='cloud-create',
366 params={'region-name': self.conf['cloud']['region'],
367 'complex-name': self.location_id,
368 'identity-url': self.conf['cloud']['identity-url'],
369 'cloud-owner': cloud_id,
370 'cloud-type': 'OpenStack',
371 'owner-type': 'ocomp',
372 'cloud-region-version': self.conf['cloud']['version'],
375 'service-url': self.conf['cloud']['identity-url'],
376 'username': self.conf['cloud']['username'],
377 'password': self.conf['cloud']['password'],
378 'system-type': 'VIM',
379 'ssl-insecure': 'true',
380 'cloud-domain': 'Default',
381 'default-tenant': self.conf['cloud']['tenant'],
382 'system-status': "active"})
383 self.cloud_id = cloud_id
386 output = self.ocomp.run(command='cloud-list')
389 if cloud['cloud'] == self.cloud_id:
390 self.cloud_version = cloud['resource-version']
394 self.ocomp.run(command='complex-associate',
395 params={'complex-name': self.location_id,
396 'cloud-region': self.conf['cloud']['region'],
397 'cloud-owner': self.cloud_id})
400 if not self.service_type_id and not self.service_type_version:
401 service_type_id = '{}-{}'.format(self.conf['subscription']['service-type'], self.conf['ONAP']['uid'])
402 self.ocomp.run(command='service-type-create',
403 params={'service-type': service_type_id,
404 'service-type-id': service_type_id})
405 self.service_type_id = service_type_id
408 output = self.ocomp.run(command='service-type-list')
411 if st['service-type'] == self.service_type_id:
412 self.service_type_version = st['resource-version']
415 if not self.customer_id and not self.customer_version:
416 customer_id = '{}-{}'.format(self.conf['subscription']['customer-name'], self.ocomp.conf['ONAP']['random'])
417 self.ocomp.run(command='customer-create',
418 params={'customer-name': customer_id,
419 'subscriber-name': customer_id})
420 self.customer_id = customer_id
423 output = self.ocomp.run(command='customer-list')
425 for customer in output:
426 if customer['name'] == self.customer_id:
427 self.customer_version = customer['resource-version']
430 if not self.tenant_id and not self.tenant_version:
431 tenant_id = str(uuid.uuid4())
432 self.ocomp.run(command='tenant-create',
433 params={'tenant-name': self.conf['cloud']['tenant'],
434 'tenant-id': tenant_id,
435 'cloud':self.cloud_id,
436 'region': self.conf['cloud']['region']})
437 self.tenant_id = tenant_id
440 output = self.ocomp.run(command='tenant-list', params={
441 'cloud': self.cloud_id,
442 'region': self.conf['cloud']['region']
445 for tenant in output:
446 if tenant['tenant-id'] == self.tenant_id:
447 self.tenant_version = tenant['resource-version']
451 self.ocomp.run(command='subscription-create',
452 params={'customer-name': self.customer_id,
453 'cloud-owner': self.cloud_id,
454 'cloud-region': self.conf['cloud']['region'],
455 'cloud-tenant-id': self.tenant_id,
456 'service-type': self.service_type_id,
457 'tenant-name': self.conf['cloud']['tenant']})
459 if not self.subscription_version:
460 output = self.ocomp.run(command='subscription-list', params={
461 'customer-name': self.customer_id
464 for subscription in output:
465 if subscription['service-type'] == self.service_type_id:
466 self.subscription_version = subscription['resource-version']
469 if not self.esr_vnfm_id and not self.esr_vnfm_version:
470 vnfmdriver = self.conf['vnf']['vnfm-driver']
472 esr_vnfm_id = str(uuid.uuid4())
473 self.ocomp.run(command='vnfm-create',
474 params={'vim-id': self.cloud_id,
475 'vnfm-id': esr_vnfm_id,
476 'name': 'OCOMP {}'.format(vnfmdriver),
478 'vendor': self.conf['vnf']['vendor-name'],
479 'vnfm-version': self.conf['vnfm'][vnfmdriver]['version'],
480 'url': self.conf['vnfm'][vnfmdriver]['url'],
481 'username': self.conf['vnfm'][vnfmdriver]['username'] ,
482 'password': self.conf['vnfm'][vnfmdriver]['password']})
483 self.esr_vnfm_id = esr_vnfm_id
485 output = self.ocomp.run(command='vnfm-list')
488 if vnfm['vnfm-id'] == self.esr_vnfm_id:
489 self.esr_vnfm_version = vnfm['resource-version']
492 # self.ocomp.run(command='multicloud-register-cloud',
493 # params={'cloud-region': self.conf['cloud']['region'],
494 # 'cloud-owner': self.cloud_id})
496 def create_vnf(self):
497 self.ocomp.run(command='vfc-catalog-onboard-vnf',
498 params={'vnf-csar-uuid': self.vf_uuid})
500 self.ocomp.run(command='vfc-catalog-onboard-ns',
501 params={'ns-csar-uuid': self.ns_uuid})
503 output = self.ocomp.run(command='vfc-nslcm-create',
504 params={'ns-csar-uuid': self.ns_uuid,
505 'ns-csar-name': '{} Service'.format(self.conf['vnf']['name']),
506 'customer-name': self.customer_id,
507 'service-type': self.service_type_id})
509 self.ns_instance_id = output['ns-instance-id']
511 vnfmdriver = self.conf['vnf']['vnfm-driver']
512 self.ocomp.run(command='vfc-nslcm-instantiate',
513 params={'ns-instance-id': self.ns_instance_id,
514 'location': self.cloud_id,
515 'sdn-controller-id': self.esr_vnfm_id})
517 def vnf_status_check(self):
518 self.vnf_status = 'active'
519 self.ns_instance_status = 'active'
522 if self.ns_instance_id:
523 self.ocomp.run(command='vfc-nslcm-terminate',
524 params={'ns-instance-id': self.ns_instance_id})
525 self.ocomp.run(command='vfc-nslcm-delete',
526 params={'ns-instance-id': self.ns_instance_id})
527 self.ns_instance_id = None
530 self.ocomp.run(command='service-model-archive',
531 params={'service-id': self.ns_id})
532 self.ns_id = self.ns_uuid = self.ns_version = self.ns_vf_resource_id = None
535 self.ocomp.run(command='vf-model-archive',
536 params={'vf-id': self.vf_id})
537 self.vf_id = self.vf_uuid = self.vf_inputs = self.vf_version = None
540 self.ocomp.run(command='vsp-archive',
541 params={'vsp-id': self.vsp_id})
542 self.vsp_id = self.vsp_version_id = self.vsp_version = None
545 self.ocomp.run(command='vlm-archive',
546 params={'vlm-id': self.vlm_id})
547 self.vlm_id = self.vlm_version = self.entitlement_id = self.key_group_id = self.feature_group_id = self.agreement_id = None
549 if self.subscription_version and self.customer_id and self.service_type_id:
550 self.ocomp.run(command='subscription-delete',
551 params={'customer-name': self.customer_id,
552 'service-type': self.service_type_id,
553 'resource-version': self.subscription_version})
554 self.subscription_version = None
556 if self.customer_id and self.customer_version:
557 self.ocomp.run(command='customer-delete',
558 params={'customer-id': self.customer_id,
559 'resource-version': self.customer_version})
560 self.customer_id = self.customer_version = None
562 if self.service_type_id and self.service_type_version:
563 output = self.ocomp.run(command='service-type-list')
566 if st['service-type-id'] == self.service_type_id:
567 self.service_type_version = st['resource-version']
570 self.ocomp.run(command='service-type-delete',
571 params={'service-type-id': self.service_type_id,
572 'resource-version': self.service_type_version})
573 self.service_type_id = self.service_type_version = None
575 if self.tenant_id and self.tenant_version:
576 self.ocomp.run(command='tenant-delete',
577 params={'cloud': self.cloud_id,
578 'region': self.conf['cloud']['region'],
579 'tenant-id': self.tenant_id,
580 'resource-version': self.tenant_version})
581 self.tenant_id = self.tenant_version = None
583 if self.cloud_id and self.location_id:
584 self.ocomp.run(command='complex-disassociate',
585 params={'cloud-owner': self.cloud_id,
586 'cloud-region': self.conf['cloud']['region'],
587 'complex-name': self.location_id})
589 if self.cloud_id and self.cloud_version:
590 output = self.ocomp.run(command='cloud-list')
593 if c['cloud'] == self.cloud_id and c['region'] == self.conf['cloud']['region']:
594 self.cloud_version = c['resource-version']
597 self.ocomp.run(command='cloud-delete',
598 params={'cloud-name': self.cloud_id,
599 'region-name': self.conf['cloud']['region'],
600 'resource-version': self.cloud_version})
601 self.cloud_id = self.cloud_version = None
603 if self.location_id and self.location_version:
604 self.ocomp.run(command='complex-delete',
605 params={'complex-name': self.location_id,
606 'resource-version': self.location_version})
607 self.location_id = self.location_version = None
609 if self.esr_vnfm_id and self.esr_vnfm_version:
610 self.ocomp.run(command='vnfm-delete',
611 params={'vnfm-id': self.esr_vnfm_id,
612 'resource-version': self.esr_vnfm_version})
613 self.esr_vnfm_id = self.esr_vnfm_version = None
616 return str(vars(self))
619 if __name__ == '__main__':
620 parser = argparse.ArgumentParser(description="ONAP TOSCA VNF validation using ONAP CLI and Open Command Platform (OCOMP)", formatter_class=RawTextHelpFormatter)
621 parser.add_argument('--product', action='store', dest='product', help='OCOMP product to use, default to onap-dublin',
622 default=os.environ.get('OPEN_CLI_PRODUCT_IN_USE'))
623 parser.add_argument('--profile', action='store', dest='profile', help='OCOMP profile to use, default to onap-dublin',
624 default=os.environ.get('OPEN_CLI_PROFILE'))
625 parser.add_argument('--request-id', action='store', dest='request_id',
626 help='Request Id to track the progress of running this script',
627 default=os.environ.get('OPEN_CLI_REQUEST_ID'))
628 parser.add_argument('--conf', action='store', dest='config_file_path', help='Configuration file path')
629 parser.add_argument('--vsp', action='store', dest='vsp', help='ONAP VSP file path')
630 parser.add_argument('--vnf-csar', action='store', dest='vnf_csar', help='TOSCA VNF CSAR file path')
631 parser.add_argument('--ns-csar', action='store', dest='ns_csar', help='TOSCA VNF CSAR file path')
632 parser.add_argument('--vnfm-driver', action='store', dest='vnfm_driver', help='VNFM dirver type one of gvnfmdriver or hwvnfmdriver',
633 choices=('gvnfmdriver', 'hwvnfmdriver'))
634 parser.add_argument('--vnf-name', action='store', dest='vnf_name', help='VNF Name')
635 parser.add_argument('--vendor-name', action='store', dest='vendor_name', help='VNF Vendor name')
636 parser.add_argument('--result-json', action='store', dest='result', help='Result json file. ' \
637 '\nInstead of creating new ONAP objects while running this script \nand to use the existing ONAP object Ids, '\
638 'use this \nresult json parameter. Object Id names are provided in configuration \nfile under ONAP section')
639 parser.add_argument('--mode', action='store', dest='mode', help='Supports 5 mode.'\
640 '\nsetup - Create the required VLM, service type, cloud, customer and \nsubscription as given in conf file' \
641 '\nstandup - Create the VSP, VF Model, Service Model and provision\n the service using VFC'\
642 '\ncleanup - Remove the ONAP objects which are either created during \nsetup and standup phase or provided by the user in result-json file ' \
643 '\nCAUTION: If required, do not provide the existing ONAP object ids \nin result-json while doing the cleanup, to avoid them getting deleted.'\
644 '\ncheckup - Check the deployment weather OCOMP is working properly or not' \
645 '\nprovision - Run thru setup -> standup' \
646 '\nvalidate - run thru setup -> standup -> cleanup modes for end to end vnf validation',
647 choices=('setup', 'standup', 'cleanup', 'checkup', 'provision', 'validate'))
649 args = parser.parse_args()
653 product = 'onap-dublin'
655 product = args.product
658 profile = 'onap-dublin'
660 profile = args.profile
662 request_id = args.request_id
664 request_id = str(uuid.uuid4())
666 vnf_csar = args.vnf_csar
667 ns_csar = args.ns_csar
674 vnfm_driver = args.vnfm_driver
676 vnfm_driver = 'gvnfmdriver'
679 vnf_name = args.vnf_name
684 vendor_name = args.vendor_name
689 config_file = args.config_file_path
690 with open(config_file) as json_file:
691 conf = json.load(json_file)
692 if not 'uid' in conf['ONAP']:
693 conf['ONAP']['uid'] = ''.join(random.sample(string.ascii_lowercase,5))
695 conf['vnf']['vsp-csar'] = vsp_csar
697 conf['vnf']['vnf-csar'] = vnf_csar
699 conf['vnf']['ns-csar'] = vnf_csar
701 conf['vnf']['name'] = vnf_name
702 conf['vnf']['name'] = '{}{}'.format(conf['vnf']['name'], conf['ONAP']['uid'])
704 conf['vnf']['vendor-name'] = vendor_name
705 conf['vnf']['vendor-name'] = '{}-{}'.format(conf['vnf']['vendor-name'], conf['ONAP']['uid'])
708 result_file = args.result
709 with open(result_file) as r_file:
710 result_json = json.load(r_file)
711 for r in result_json:
712 if r in conf['ONAP']:
713 conf['ONAP'][r] = result_json[r]
717 print (OCOMP.version())
719 onap = ONAP(product, profile, conf, request_id)
723 onap.setup_cloud_and_subscription()
727 onap.create_vf_model()
728 onap.create_service_model()
730 onap.vnf_status_check()
738 elif mode == 'standup':
740 elif mode == 'cleanup':
742 elif mode == 'checkup':
743 onap.ocomp.product = 'open-cli'
744 onap.ocomp.run(command='schema-list', params={'product': 'open-cli'})
745 elif mode == 'provision':
748 elif mode == 'validate':
755 onap_result = json.dumps(onap, default=lambda x: x.__dict__)
759 #Remove conf and ocomp from the onap object
760 for attr in ['ocomp', 'tag', 'conf']:
763 with open(result_file, "w") as f:
764 f.write(json.dumps(onap, default=lambda x: x.__dict__))