From a1d829009dd89010eaf01a0bb62e96b681951cef Mon Sep 17 00:00:00 2001 From: hewei Date: Wed, 20 Feb 2019 16:40:19 +0800 Subject: [PATCH] Replace datasource variable with constant Replace cinder/heat/neutron/nova transformer variable with constant. Change-Id: I968b22a58ee9adb733798f6cd3ef78d61aaa6264 --- .../datasources/cinder/volume/properties.py | 10 ++++ .../datasources/cinder/volume/transformer.py | 54 +++++++++++-------- vitrage/datasources/heat/stack/properties.py | 28 ++++++++++ vitrage/datasources/heat/stack/transformer.py | 33 +++++++----- .../neutron/network/transformer.py | 30 ++++++----- .../datasources/neutron/port/transformer.py | 42 +++++++++------ vitrage/datasources/neutron/properties.py | 39 ++++++++++++++ vitrage/datasources/nova/host/transformer.py | 8 +-- vitrage/datasources/nova/properties.py | 25 +++++++++ vitrage/datasources/nova/zone/transformer.py | 13 ++--- 10 files changed, 208 insertions(+), 74 deletions(-) create mode 100644 vitrage/datasources/heat/stack/properties.py create mode 100644 vitrage/datasources/neutron/properties.py create mode 100644 vitrage/datasources/nova/properties.py diff --git a/vitrage/datasources/cinder/volume/properties.py b/vitrage/datasources/cinder/volume/properties.py index 56e66d802..6ac9fba6e 100644 --- a/vitrage/datasources/cinder/volume/properties.py +++ b/vitrage/datasources/cinder/volume/properties.py @@ -17,3 +17,13 @@ class CinderProperties(object): SIZE = 'size' VOLUME_TYPE = 'volume_type' ATTACHMENTS = 'attachments' + DISPLAY_NAME = 'display_name' + ID = 'id' + STATUS = 'status' + CREATED_AT = 'created_at' + VOLUME_ATTACHMENT = 'volume_attachment' + INSTANCE_UUID = 'instance_uuid' + SERVER_ID = 'server_id' + VOLUME_ID = 'volume_id' + UPDATE_AT = 'updated_at' + TENANT_ID = 'tenant_id' diff --git a/vitrage/datasources/cinder/volume/transformer.py b/vitrage/datasources/cinder/volume/transformer.py index cb8223473..ad555c713 100644 --- a/vitrage/datasources/cinder/volume/transformer.py +++ b/vitrage/datasources/cinder/volume/transformer.py @@ -44,14 +44,18 @@ class CinderVolumeTransformer(ResourceTransformerBase): def _create_snapshot_entity_vertex(self, entity_event): - volume_name = extract_field_value(entity_event, 'display_name') - volume_id = extract_field_value(entity_event, 'id') - volume_state = extract_field_value(entity_event, 'status') - project_id = entity_event.get('os-vol-tenant-attr:tenant_id', None) - timestamp = extract_field_value(entity_event, 'created_at') - size = extract_field_value(entity_event, 'size') - volume_type = extract_field_value(entity_event, 'volume_type') - attachments = extract_field_value(entity_event, 'attachments') + volume_name = extract_field_value(entity_event, + CinderProps.DISPLAY_NAME) + volume_id = extract_field_value(entity_event, CinderProps.ID) + volume_state = extract_field_value(entity_event, CinderProps.STATUS) + project_id = entity_event.get( + 'os-vol-tenant-attr:%s' % CinderProps.TENANT_ID, None) + timestamp = extract_field_value(entity_event, CinderProps.CREATED_AT) + size = extract_field_value(entity_event, CinderProps.SIZE) + volume_type = extract_field_value(entity_event, + CinderProps.VOLUME_TYPE) + attachments = extract_field_value(entity_event, + CinderProps.ATTACHMENTS) return self._create_vertex(entity_event, volume_name, @@ -62,18 +66,21 @@ class CinderVolumeTransformer(ResourceTransformerBase): size, volume_type, attachments, - 'server_id') + CinderProps.SERVER_ID) def _create_update_entity_vertex(self, entity_event): - volume_name = extract_field_value(entity_event, 'display_name') - volume_id = extract_field_value(entity_event, 'volume_id') - volume_state = extract_field_value(entity_event, 'status') - project_id = entity_event.get('tenant_id', None) - timestamp = entity_event.get('updated_at', None) - size = extract_field_value(entity_event, 'size') - volume_type = extract_field_value(entity_event, 'volume_type') - attachments = extract_field_value(entity_event, 'volume_attachment') + volume_name = extract_field_value(entity_event, + CinderProps.DISPLAY_NAME) + volume_id = extract_field_value(entity_event, CinderProps.VOLUME_ID) + volume_state = extract_field_value(entity_event, CinderProps.STATUS) + project_id = entity_event.get(CinderProps.TENANT_ID, None) + timestamp = entity_event.get(CinderProps.UPDATE_AT, None) + size = extract_field_value(entity_event, CinderProps.SIZE) + volume_type = extract_field_value(entity_event, + CinderProps.VOLUME_TYPE) + attachments = extract_field_value(entity_event, + CinderProps.VOLUME_ATTACHMENT) return self._create_vertex(entity_event, volume_name, @@ -84,7 +91,7 @@ class CinderVolumeTransformer(ResourceTransformerBase): size, volume_type, attachments, - 'instance_uuid') + CinderProps.INSTANCE_UUID) def _create_vertex(self, entity_event, @@ -127,18 +134,19 @@ class CinderVolumeTransformer(ResourceTransformerBase): def _create_snapshot_neighbors(self, entity_event): return self._create_volume_neighbors(entity_event, - 'attachments', - 'server_id') + CinderProps.ATTACHMENTS, + CinderProps.SERVER_ID) def _create_update_neighbors(self, entity_event): return self._create_volume_neighbors(entity_event, - 'volume_attachment', - 'instance_uuid') + CinderProps.VOLUME_ATTACHMENT, + CinderProps.INSTANCE_UUID) def _create_entity_key(self, entity_event): is_update_event = tbase.is_update_event(entity_event) - id_field_path = 'volume_id' if is_update_event else 'id' + id_field_path = CinderProps.VOLUME_ID \ + if is_update_event else CinderProps.ID volume_id = extract_field_value(entity_event, id_field_path) key_fields = self._key_values(CINDER_VOLUME_DATASOURCE, volume_id) diff --git a/vitrage/datasources/heat/stack/properties.py b/vitrage/datasources/heat/stack/properties.py new file mode 100644 index 000000000..171ad59aa --- /dev/null +++ b/vitrage/datasources/heat/stack/properties.py @@ -0,0 +1,28 @@ +# Copyright 2019 - China Mobile (SuZhou) Software Technology Co.,Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class StackProperties(object): + STACK_NAME = 'stack_name' + STACK_STATUS = 'stack_status' + ID = 'id' + STATE = 'state' + CREATED_AT = 'created_at' + STACK_IDENTITY = 'stack_identity' + CREATION_TIME = 'creation_time' + PROJECT = 'project' + RESOURCES = 'resources' + RESOURCES_TYPE = 'resource_type' + PHYSICAL_RESOURCE_ID = 'physical_resource_id' + TENANT_ID = 'tenant_id' diff --git a/vitrage/datasources/heat/stack/transformer.py b/vitrage/datasources/heat/stack/transformer.py index 30d030b94..ce6bcd2fc 100644 --- a/vitrage/datasources/heat/stack/transformer.py +++ b/vitrage/datasources/heat/stack/transformer.py @@ -19,6 +19,8 @@ from vitrage.common.constants import GraphAction from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.cinder.volume import CINDER_VOLUME_DATASOURCE from vitrage.datasources.heat.stack import HEAT_STACK_DATASOURCE +from vitrage.datasources.heat.stack.properties import StackProperties\ + as StackProps from vitrage.datasources.neutron.network import NEUTRON_NETWORK_DATASOURCE from vitrage.datasources.neutron.port import NEUTRON_PORT_DATASOURCE from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE @@ -49,11 +51,12 @@ class HeatStackTransformer(ResourceTransformerBase): def _create_snapshot_entity_vertex(self, entity_event): - stack_name = extract_field_value(entity_event, 'stack_name') - stack_id = extract_field_value(entity_event, 'id') - stack_state = extract_field_value(entity_event, 'stack_status') - timestamp = extract_field_value(entity_event, 'creation_time') - project_id = extract_field_value(entity_event, 'project') + stack_name = extract_field_value(entity_event, StackProps.STACK_NAME) + stack_id = extract_field_value(entity_event, StackProps.ID) + stack_state = extract_field_value(entity_event, + StackProps.STACK_STATUS) + timestamp = extract_field_value(entity_event, StackProps.CREATION_TIME) + project_id = extract_field_value(entity_event, StackProps.PROJECT) return self._create_vertex(entity_event, stack_name, @@ -64,11 +67,12 @@ class HeatStackTransformer(ResourceTransformerBase): def _create_update_entity_vertex(self, entity_event): - volume_name = extract_field_value(entity_event, 'stack_name') - volume_id = extract_field_value(entity_event, 'stack_identity') - volume_state = extract_field_value(entity_event, 'state') - timestamp = entity_event.get('create_at', None) - project_id = entity_event.get('tenant_id', None) + volume_name = extract_field_value(entity_event, StackProps.STACK_NAME) + volume_id = extract_field_value(entity_event, + StackProps.STACK_IDENTITY) + volume_state = extract_field_value(entity_event, StackProps.STATE) + timestamp = entity_event.get(StackProps.CREATED_AT, None) + project_id = entity_event.get(StackProps.TENANT_ID, None) return self._create_vertex(entity_event, volume_name, @@ -112,7 +116,8 @@ class HeatStackTransformer(ResourceTransformerBase): def _create_entity_key(self, entity_event): is_update_event = tbase.is_update_event(entity_event) - id_field_path = 'stack_identity' if is_update_event else 'id' + id_field_path = StackProps.STACK_IDENTITY\ + if is_update_event else StackProps.ID volume_id = extract_field_value(entity_event, id_field_path) key_fields = self._key_values(HEAT_STACK_DATASOURCE, volume_id) @@ -121,10 +126,10 @@ class HeatStackTransformer(ResourceTransformerBase): def _create_stack_neighbors(self, entity_event): neighbors = [] - for neighbor in entity_event['resources']: - neighbor_id = neighbor['physical_resource_id'] + for neighbor in entity_event[StackProps.RESOURCES]: + neighbor_id = neighbor[StackProps.PHYSICAL_RESOURCE_ID] neighbor_datasource_type = \ - self.RESOURCE_TYPE[neighbor['resource_type']] + self.RESOURCE_TYPE[neighbor[StackProps.RESOURCES_TYPE]] neighbors.append(self._create_neighbor(entity_event, neighbor_id, neighbor_datasource_type, diff --git a/vitrage/datasources/neutron/network/transformer.py b/vitrage/datasources/neutron/network/transformer.py index bb45e8f52..ca40ab74c 100644 --- a/vitrage/datasources/neutron/network/transformer.py +++ b/vitrage/datasources/neutron/network/transformer.py @@ -17,6 +17,8 @@ from vitrage.common.constants import EntityCategory from vitrage.common.constants import GraphAction from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.neutron.network import NEUTRON_NETWORK_DATASOURCE +from vitrage.datasources.neutron.properties import NetworkProperties\ + as NetworkProps from vitrage.datasources.resource_transformer_base import \ ResourceTransformerBase from vitrage.datasources import transformer_base as tbase @@ -27,10 +29,10 @@ import vitrage.graph.utils as graph_utils class NetworkTransformer(ResourceTransformerBase): UPDATE_ID_PROPERTY = { - 'network.create.end': ('network', 'id'), - 'network.update.end': ('network', 'id'), + 'network.create.end': (NetworkProps.NETWORK, NetworkProps.ID), + 'network.update.end': (NetworkProps.NETWORK, NetworkProps.ID), 'network.delete.end': ('network_id',), - None: ('id',) + None: (NetworkProps.ID,) } # graph actions which need to refer them differently @@ -43,11 +45,11 @@ class NetworkTransformer(ResourceTransformerBase): def _create_snapshot_entity_vertex(self, entity_event): - name = entity_event['name'] - entity_id = entity_event['id'] - state = entity_event['status'] - update_timestamp = entity_event['updated_at'] - project_id = entity_event.get('tenant_id', None) + name = entity_event[NetworkProps.NAME] + entity_id = entity_event[NetworkProps.ID] + state = entity_event[NetworkProps.STATUS] + update_timestamp = entity_event[NetworkProps.UPDATED_AT] + project_id = entity_event.get(NetworkProps.TENANT_ID, None) return self._create_vertex(entity_event, name, @@ -59,13 +61,17 @@ class NetworkTransformer(ResourceTransformerBase): def _create_update_entity_vertex(self, entity_event): event_type = entity_event[DSProps.EVENT_TYPE] - name = extract_field_value(entity_event, 'network', 'name') - state = extract_field_value(entity_event, 'network', 'status') + name = extract_field_value(entity_event, NetworkProps.NETWORK, + NetworkProps.NAME) + state = extract_field_value(entity_event, NetworkProps.NETWORK, + NetworkProps.STATUS) update_timestamp = \ - extract_field_value(entity_event, 'network', 'updated_at') + extract_field_value(entity_event, NetworkProps.NETWORK, + NetworkProps.UPDATED_AT) entity_id = extract_field_value(entity_event, *self.UPDATE_ID_PROPERTY[event_type]) - project_id = extract_field_value(entity_event, 'network', 'tenant_id') + project_id = extract_field_value(entity_event, NetworkProps.NETWORK, + NetworkProps.TENANT_ID) return self._create_vertex(entity_event, name, diff --git a/vitrage/datasources/neutron/port/transformer.py b/vitrage/datasources/neutron/port/transformer.py index 9142f16d0..edb5bd4ab 100644 --- a/vitrage/datasources/neutron/port/transformer.py +++ b/vitrage/datasources/neutron/port/transformer.py @@ -22,6 +22,8 @@ from vitrage.common.constants import GraphAction from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.neutron.network import NEUTRON_NETWORK_DATASOURCE from vitrage.datasources.neutron.port import NEUTRON_PORT_DATASOURCE +from vitrage.datasources.neutron.properties import PortProperties\ + as PortProps from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE from vitrage.datasources import transformer_base as tbase from vitrage.datasources.transformer_base import extract_field_value @@ -54,11 +56,12 @@ class PortTransformer(ResourceTransformerBase): def _create_snapshot_entity_vertex(self, entity_event): - name = entity_event['name'] if entity_event['name'] else None - entity_id = entity_event['id'] - state = entity_event['status'] - update_timestamp = entity_event['updated_at'] - project_id = entity_event.get('tenant_id', None) + name = entity_event[PortProps.NAME]\ + if entity_event[PortProps.NAME] else None + entity_id = entity_event[PortProps.ID] + state = entity_event[PortProps.STATUS] + update_timestamp = entity_event[PortProps.UPDATED_AT] + project_id = entity_event.get(PortProps.TENANT_ID, None) return self._create_vertex(entity_event, name, @@ -70,13 +73,17 @@ class PortTransformer(ResourceTransformerBase): def _create_update_entity_vertex(self, entity_event): event_type = entity_event[DSProps.EVENT_TYPE] - name = extract_field_value(entity_event, 'port', 'name') - state = extract_field_value(entity_event, 'port', 'status') + name = extract_field_value(entity_event, PortProps.PORT, + PortProps.NAME) + state = extract_field_value(entity_event, PortProps.PORT, + PortProps.STATUS) update_timestamp = \ - extract_field_value(entity_event, 'port', 'updated_at') + extract_field_value(entity_event, PortProps.PORT, + PortProps.UPDATED_AT) entity_id = extract_field_value(entity_event, *self.UPDATE_ID_PROPERTY[event_type]) - project_id = extract_field_value(entity_event, 'port', 'tenant_id') + project_id = extract_field_value(entity_event, PortProps.PORT, + PortProps.TENANT_ID) return self._create_vertex(entity_event, name, @@ -97,12 +104,13 @@ class PortTransformer(ResourceTransformerBase): if not event_type: fixed_ips = extract_field_value( entity_event, *self.FIXED_IPS_PROPERTY[event_type]) - ip_addresses = [ip['ip_address'] for ip in fixed_ips] + ip_addresses = [ip[PortProps.IP_ADDRESS] for ip in fixed_ips] metadata = { VProps.NAME: name, VProps.PROJECT_ID: project_id, - 'ip_addresses': tuple(ip_addresses), - 'host_id': entity_event.get('binding:host_id'), + PortProps.IP_ADDRESSES: tuple(ip_addresses), + PortProps.HOST_ID: entity_event.get( + 'binding:%s' % PortProps.HOST_ID), } vitrage_sample_timestamp = entity_event[DSProps.SAMPLE_DATE] @@ -119,13 +127,15 @@ class PortTransformer(ResourceTransformerBase): def _create_snapshot_neighbors(self, entity_event): return self._create_port_neighbors(entity_event, - ('device_id',), - ('network_id',)) + (PortProps.DEVICE_ID,), + (PortProps.NETWORK_ID,)) def _create_update_neighbors(self, entity_event): return self._create_port_neighbors(entity_event, - ('port', 'device_id'), - ('port', 'network_id')) + (PortProps.PORT, + PortProps.DEVICE_ID), + (PortProps.PORT, + PortProps.NETWORK_ID)) def _create_port_neighbors(self, entity_event, diff --git a/vitrage/datasources/neutron/properties.py b/vitrage/datasources/neutron/properties.py new file mode 100644 index 000000000..e071f49a1 --- /dev/null +++ b/vitrage/datasources/neutron/properties.py @@ -0,0 +1,39 @@ +# Copyright 2019 - China Mobile (SuZhou) Software Technology Co.,Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class NetworkProperties(object): + NAME = 'name' + STATUS = 'status' + ID = 'id' + TENANT_ID = 'tenant_id' + CREATED_AT = 'created_at' + UPDATED_AT = 'updated_at' + NETWORK = 'network' + + +class PortProperties(object): + NAME = 'name' + STATUS = 'status' + ID = 'id' + TENANT_ID = 'tenant_id' + CREATED_AT = 'created_at' + UPDATED_AT = 'updated_at' + NETWORK = 'network' + DEVICE_ID = 'device_id' + NETWORK_ID = 'network_id' + HOST_ID = 'host_id' + PORT = 'port' + IP_ADDRESS = 'ip_address' + IP_ADDRESSES = 'ip_addresses' diff --git a/vitrage/datasources/nova/host/transformer.py b/vitrage/datasources/nova/host/transformer.py index ac414ba8a..af2e8f3cc 100644 --- a/vitrage/datasources/nova/host/transformer.py +++ b/vitrage/datasources/nova/host/transformer.py @@ -19,6 +19,7 @@ from vitrage.common.constants import EdgeLabel from vitrage.common.constants import EntityCategory from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE +from vitrage.datasources.nova.properties import HostProperties as HostProps from vitrage.datasources.nova.zone import NOVA_ZONE_DATASOURCE from vitrage.datasources.resource_transformer_base import \ ResourceTransformerBase @@ -36,7 +37,8 @@ class HostTransformer(ResourceTransformerBase): super(HostTransformer, self).__init__(transformers, conf) def _create_snapshot_entity_vertex(self, entity_event): - return self._create_vertex(entity_event, entity_event.get('host')) + return self._create_vertex(entity_event, + entity_event.get(HostProps.HOST)) def _create_update_entity_vertex(self, entity_event): LOG.warning('Host Update is not supported yet') @@ -67,7 +69,7 @@ class HostTransformer(ResourceTransformerBase): return self._create_host_neighbors(entity_event) def _create_host_neighbors(self, entity_event): - zone_name = extract_field_value(entity_event, 'zone') + zone_name = extract_field_value(entity_event, HostProps.ZONE) zone_neighbor = self._create_neighbor(entity_event, zone_name, NOVA_ZONE_DATASOURCE, @@ -77,7 +79,7 @@ class HostTransformer(ResourceTransformerBase): def _create_entity_key(self, entity_event): key_fields = self._key_values(NOVA_HOST_DATASOURCE, - entity_event.get('host')) + entity_event.get(HostProps.HOST)) return transformer_base.build_key(key_fields) def get_vitrage_type(self): diff --git a/vitrage/datasources/nova/properties.py b/vitrage/datasources/nova/properties.py new file mode 100644 index 000000000..4ddb7099f --- /dev/null +++ b/vitrage/datasources/nova/properties.py @@ -0,0 +1,25 @@ +# Copyright 2019 - China Mobile (SuZhou) Software Technology Co.,Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class HostProperties(object): + HOST = 'host' + ZONE = 'zone' + + +class ZoneProperties(object): + ZONE_NAME = 'zoneName' + ZONE_STATE = 'zoneState' + NOVE_COMPUTE = 'nova-compute' + ACTIVE = 'active' diff --git a/vitrage/datasources/nova/zone/transformer.py b/vitrage/datasources/nova/zone/transformer.py index 55c731d20..25fb4e7c2 100644 --- a/vitrage/datasources/nova/zone/transformer.py +++ b/vitrage/datasources/nova/zone/transformer.py @@ -19,6 +19,7 @@ from vitrage.common.constants import EdgeLabel from vitrage.common.constants import EntityCategory from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE +from vitrage.datasources.nova.properties import ZoneProperties as ZoneProps from vitrage.datasources.nova.zone import NOVA_ZONE_DATASOURCE from vitrage.datasources import OPENSTACK_CLUSTER from vitrage.datasources.resource_transformer_base import \ @@ -42,9 +43,9 @@ class ZoneTransformer(ResourceTransformerBase): def _create_snapshot_entity_vertex(self, entity_event): - zone_name = extract_field_value(entity_event, 'zoneName') + zone_name = extract_field_value(entity_event, ZoneProps.ZONE_NAME) is_available = extract_field_value(entity_event, - 'zoneState', + ZoneProps.ZONE_STATE, 'available') state = self.STATE_AVAILABLE if is_available \ else self.STATE_UNAVAILABLE @@ -102,11 +103,11 @@ class ZoneTransformer(ResourceTransformerBase): for hostname, host_data in hosts.items(): host_available = extract_field_value(host_data, - 'nova-compute', + ZoneProps.NOVE_COMPUTE, 'available') host_active = extract_field_value(host_data, - 'nova-compute', - 'active') + ZoneProps.NOVE_COMPUTE, + ZoneProps.ACTIVE) host_state = self.STATE_AVAILABLE \ if host_available and host_active \ @@ -129,7 +130,7 @@ class ZoneTransformer(ResourceTransformerBase): def _create_entity_key(self, entity_event): - zone_name = extract_field_value(entity_event, 'zoneName') + zone_name = extract_field_value(entity_event, ZoneProps.ZONE_NAME) key_fields = self._key_values(NOVA_ZONE_DATASOURCE, zone_name) return tbase.build_key(key_fields)