summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/compute/contrib/hosts.py78
-rw-r--r--nova/compute/api.py157
-rw-r--r--nova/db/sqlalchemy/api.py9
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_hosts.py10
-rw-r--r--nova/tests/compute/test_compute.py75
-rw-r--r--nova/tests/compute/test_host_api.py175
6 files changed, 224 insertions, 280 deletions
diff --git a/nova/api/openstack/compute/contrib/hosts.py b/nova/api/openstack/compute/contrib/hosts.py
index 52487c305..d1b39d6db 100644
--- a/nova/api/openstack/compute/contrib/hosts.py
+++ b/nova/api/openstack/compute/contrib/hosts.py
@@ -124,10 +124,17 @@ class HostController(object):
"""
context = req.environ['nova.context']
authorize(context)
+ filters = {}
zone = req.GET.get('zone', None)
- data = self.api.list_hosts(context, zone)
-
- return {'hosts': data}
+ if zone:
+ filters['availability_zone'] = zone
+ services = self.api.service_get_all(context, filters=filters)
+ hosts = []
+ for service in services:
+ hosts.append({'host_name': service['host'],
+ 'service': service['topic'],
+ 'zone': service['availability_zone']})
+ return {'hosts': hosts}
@wsgi.serializers(xml=HostUpdateTemplate)
@wsgi.deserializers(xml=HostUpdateDeserializer)
@@ -243,6 +250,55 @@ class HostController(object):
def reboot(self, req, id):
return self._host_power_action(req, host_name=id, action="reboot")
+ @staticmethod
+ def _get_total_resources(host_name, compute_node):
+ return {'resource': {'host': host_name,
+ 'project': '(total)',
+ 'cpu': compute_node['vcpus'],
+ 'memory_mb': compute_node['memory_mb'],
+ 'disk_gb': compute_node['local_gb']}}
+
+ @staticmethod
+ def _get_used_now_resources(host_name, compute_node):
+ return {'resource': {'host': host_name,
+ 'project': '(used_now)',
+ 'cpu': compute_node['vcpus_used'],
+ 'memory_mb': compute_node['memory_mb_used'],
+ 'disk_gb': compute_node['local_gb_used']}}
+
+ @staticmethod
+ def _get_resource_totals_from_instances(host_name, instances):
+ cpu_sum = 0
+ mem_sum = 0
+ hdd_sum = 0
+ for instance in instances:
+ cpu_sum += instance['vcpus']
+ mem_sum += instance['memory_mb']
+ hdd_sum += instance['root_gb'] + instance['ephemeral_gb']
+
+ return {'resource': {'host': host_name,
+ 'project': '(used_max)',
+ 'cpu': cpu_sum,
+ 'memory_mb': mem_sum,
+ 'disk_gb': hdd_sum}}
+
+ @staticmethod
+ def _get_resources_by_project(host_name, instances):
+ # Getting usage resource per project
+ project_map = {}
+ for instance in instances:
+ resource = project_map.setdefault(instance['project_id'],
+ {'host': host_name,
+ 'project': instance['project_id'],
+ 'cpu': 0,
+ 'memory_mb': 0,
+ 'disk_gb': 0})
+ resource['cpu'] += instance['vcpus']
+ resource['memory_mb'] += instance['memory_mb']
+ resource['disk_gb'] += (instance['root_gb'] +
+ instance['ephemeral_gb'])
+ return project_map
+
@wsgi.serializers(xml=HostShowTemplate)
def show(self, req, id):
"""Shows the physical/usage resource given by hosts.
@@ -256,14 +312,26 @@ class HostController(object):
'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}
"""
context = req.environ['nova.context']
+ host_name = id
try:
- data = self.api.describe_host(context, id)
+ service = self.api.service_get_by_compute_host(context, host_name)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.message)
except exception.AdminRequired:
msg = _("Describe-resource is admin only functionality")
raise webob.exc.HTTPForbidden(explanation=msg)
- return {'host': data}
+ compute_node = service['compute_node'][0]
+ instances = self.api.instance_get_all_by_host(context, host_name)
+ resources = [self._get_total_resources(host_name, compute_node)]
+ resources.append(self._get_used_now_resources(host_name,
+ compute_node))
+ resources.append(self._get_resource_totals_from_instances(host_name,
+ instances))
+ by_proj_resources = self._get_resources_by_project(host_name,
+ instances)
+ for resource in by_proj_resources.itervalues():
+ resources.append({'resource': resource})
+ return {'host': resources}
class Hosts(extensions.ExtensionDescriptor):
diff --git a/nova/compute/api.py b/nova/compute/api.py
index bfb5c6195..9ccd35573 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2187,139 +2187,76 @@ class API(base.Base):
disk_over_commit, instance, host_name)
-def check_host(fn):
- """Decorator that makes sure that the host exists."""
- def wrapped(self, context, host_name, *args, **kwargs):
- if self.does_host_exist(context, host_name):
- return fn(self, context, host_name, *args, **kwargs)
- else:
- raise exception.HostNotFound(host=host_name)
- return wrapped
-
-
class HostAPI(base.Base):
"""Sub-set of the Compute Manager API for managing host operations."""
- def __init__(self):
- self.compute_rpcapi = compute_rpcapi.ComputeAPI()
+ def __init__(self, rpcapi=None):
+ self.rpcapi = rpcapi or compute_rpcapi.ComputeAPI()
super(HostAPI, self).__init__()
- @check_host
+ def _assert_host_exists(self, context, host_name):
+ """Raise HostNotFound if compute host doesn't exist."""
+ if not self.db.service_get_by_host_and_topic(context, host_name,
+ CONF.compute_topic):
+ raise exception.HostNotFound(host=host_name)
+
def set_host_enabled(self, context, host_name, enabled):
"""Sets the specified host's ability to accept new instances."""
# NOTE(comstud): No instance_uuid argument to this compute manager
# call
- return self.compute_rpcapi.set_host_enabled(context, enabled=enabled,
+ self._assert_host_exists(context, host_name)
+ return self.rpcapi.set_host_enabled(context, enabled=enabled,
host=host_name)
- @check_host
def get_host_uptime(self, context, host_name):
"""Returns the result of calling "uptime" on the target host."""
# NOTE(comstud): No instance_uuid argument to this compute manager
# call
- return self.compute_rpcapi.get_host_uptime(context, host=host_name)
+ self._assert_host_exists(context, host_name)
+ return self.rpcapi.get_host_uptime(context, host=host_name)
- @check_host
def host_power_action(self, context, host_name, action):
"""Reboots, shuts down or powers up the host."""
- return self.compute_rpcapi.host_power_action(context, action=action,
+ self._assert_host_exists(context, host_name)
+ return self.rpcapi.host_power_action(context, action=action,
host=host_name)
- def list_hosts(self, context, zone=None, service=None):
- """Returns a summary list of enabled hosts, optionally filtering
- by zone and/or service type.
+ def set_host_maintenance(self, context, host_name, mode):
+ """Start/Stop host maintenance window. On start, it triggers
+ guest VMs evacuation."""
+ self._assert_host_exists(context, host_name)
+ return self.rpcapi.host_maintenance_mode(context,
+ host_param=host_name, mode=mode, host=host_name)
+
+ def service_get_all(self, context, filters=None):
+ """Returns a list of services, optionally filtering the results.
+
+ If specified, 'filters' should be a dictionary containing services
+ attributes and matching values. Ie, to get a list of services for
+ the 'compute' topic, use filters={'topic': 'compute'}.
"""
- LOG.debug(_("Listing hosts"))
+ if filters is None:
+ filters = {}
services = self.db.service_get_all(context, False)
- services = availability_zones.set_availability_zones(context, services)
- if zone:
- services = [s for s in services if s['availability_zone'] == zone]
- hosts = []
- for host in services:
- hosts.append({'host_name': host['host'], 'service': host['topic'],
- 'zone': host['availability_zone']})
- if service:
- hosts = [host for host in hosts
- if host["service"] == service]
- return hosts
-
- def does_host_exist(self, context, host_name):
- """
- Returns True if the host with host_name exists, False otherwise
- """
- return self.db.service_does_host_exist(context, host_name)
+ services = availability_zones.set_availability_zones(context,
+ services)
+ ret_services = []
+ for service in services:
+ for key, val in filters.iteritems():
+ if service[key] != val:
+ break
+ else:
+ # All filters matched.
+ ret_services.append(service)
+ return ret_services
- def describe_host(self, context, host_name):
- """
- Returns information about a host in this kind of format:
- :returns:
- ex.::
- {'host': 'hostname',
- 'project': 'admin',
- 'cpu': 1,
- 'memory_mb': 2048,
- 'disk_gb': 30}
- """
- # Getting compute node info and related instances info
- try:
- compute_ref = self.db.service_get_by_compute_host(context,
- host_name)
- except exception.ComputeHostNotFound:
- raise exception.HostNotFound(host=host_name)
- instance_refs = self.db.instance_get_all_by_host(context,
- compute_ref['host'])
-
- # Getting total available/used resource
- compute_ref = compute_ref['compute_node'][0]
- resources = [{'resource': {'host': host_name, 'project': '(total)',
- 'cpu': compute_ref['vcpus'],
- 'memory_mb': compute_ref['memory_mb'],
- 'disk_gb': compute_ref['local_gb']}},
- {'resource': {'host': host_name, 'project': '(used_now)',
- 'cpu': compute_ref['vcpus_used'],
- 'memory_mb': compute_ref['memory_mb_used'],
- 'disk_gb': compute_ref['local_gb_used']}}]
-
- cpu_sum = 0
- mem_sum = 0
- hdd_sum = 0
- for i in instance_refs:
- cpu_sum += i['vcpus']
- mem_sum += i['memory_mb']
- hdd_sum += i['root_gb'] + i['ephemeral_gb']
-
- resources.append({'resource': {'host': host_name,
- 'project': '(used_max)',
- 'cpu': cpu_sum,
- 'memory_mb': mem_sum,
- 'disk_gb': hdd_sum}})
-
- # Getting usage resource per project
- project_ids = [i['project_id'] for i in instance_refs]
- project_ids = list(set(project_ids))
- for project_id in project_ids:
- vcpus = [i['vcpus'] for i in instance_refs
- if i['project_id'] == project_id]
-
- mem = [i['memory_mb'] for i in instance_refs
- if i['project_id'] == project_id]
-
- disk = [i['root_gb'] + i['ephemeral_gb'] for i in instance_refs
- if i['project_id'] == project_id]
-
- resources.append({'resource': {'host': host_name,
- 'project': project_id,
- 'cpu': sum(vcpus),
- 'memory_mb': sum(mem),
- 'disk_gb': sum(disk)}})
- return resources
-
- @check_host
- def set_host_maintenance(self, context, host, mode):
- """Start/Stop host maintenance window. On start, it triggers
- guest VMs evacuation."""
- return self.compute_rpcapi.host_maintenance_mode(context,
- host_param=host, mode=mode, host=host)
+ def service_get_by_compute_host(self, context, host_name):
+ """Get service entry for the given compute hostname."""
+ return self.db.service_get_by_compute_host(context, host_name)
+
+ def instance_get_all_by_host(self, context, host_name):
+ """Return all instances on the given host."""
+ return self.db.instance_get_all_by_host(context, host_name)
class AggregateAPI(base.Base):
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index e51d7b685..5cac0b065 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -337,15 +337,6 @@ def service_get_all(context, disabled=None):
@require_admin_context
-def service_does_host_exist(context, host_name, include_disabled):
- query = get_session().query(func.count(models.Service.host)).\
- filter_by(host=host_name)
- if not include_disabled:
- query = query.filter_by(disabled=False)
- return query.scalar() > 0
-
-
-@require_admin_context
def service_get_all_by_topic(context, topic):
return model_query(context, models.Service, read_deleted="no").\
filter_by(disabled=False).\
diff --git a/nova/tests/api/openstack/compute/contrib/test_hosts.py b/nova/tests/api/openstack/compute/contrib/test_hosts.py
index be4465cf9..e103b5b19 100644
--- a/nova/tests/api/openstack/compute/contrib/test_hosts.py
+++ b/nova/tests/api/openstack/compute/contrib/test_hosts.py
@@ -32,8 +32,10 @@ def stub_service_get_all(context, disabled=None):
return fake_hosts.SERVICES_LIST
-def stub_service_does_host_exist(context, host_name):
- return host_name in [row['host'] for row in stub_service_get_all(context)]
+def stub_service_get_by_host_and_topic(context, host_name, topic):
+ for service in stub_service_get_all(context):
+ if service['host'] == host_name and service['topic'] == topic:
+ return service
def stub_set_host_enabled(context, host_name, enabled):
@@ -130,8 +132,8 @@ class HostTestCase(test.TestCase):
self.stubs.Set(db, 'service_get_all',
stub_service_get_all)
# Only hosts in our fake DB exist
- self.stubs.Set(db, 'service_does_host_exist',
- stub_service_does_host_exist)
+ self.stubs.Set(db, 'service_get_by_host_and_topic',
+ stub_service_get_by_host_and_topic)
# 'host_c1' always succeeds, and 'host_c2'
self.stubs.Set(self.hosts_api, 'set_host_enabled',
stub_set_host_enabled)
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 08d9451b3..9dedd782b 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -6090,81 +6090,6 @@ class ComputePolicyTestCase(BaseTestCase):
availability_zone='1:1')
-class ComputeHostAPITestCase(BaseTestCase):
- def setUp(self):
- super(ComputeHostAPITestCase, self).setUp()
- self.host_api = compute_api.HostAPI()
-
- def _rpc_call_stub(self, call_info):
- def fake_rpc_call(context, topic, msg, timeout=None):
- call_info['context'] = context
- call_info['topic'] = topic
- call_info['msg'] = msg
- self.stubs.Set(rpc, 'call', fake_rpc_call)
-
- def _pretend_fake_host_exists(self, ctxt):
- """Sets it so that the host API always thinks that 'fake_host'
- exists"""
- self.mox.StubOutWithMock(self.host_api, 'does_host_exist')
- self.host_api.does_host_exist(ctxt, 'fake_host').AndReturn(True)
- self.mox.ReplayAll()
-
- def test_set_host_enabled(self):
- ctxt = context.get_admin_context()
- call_info = {}
- self._rpc_call_stub(call_info)
-
- self._pretend_fake_host_exists(ctxt)
- self.host_api.set_host_enabled(ctxt, 'fake_host', 'fake_enabled')
- self.assertEqual(call_info['context'], ctxt)
- self.assertEqual(call_info['topic'], 'compute.fake_host')
- self.assertEqual(call_info['msg'],
- {'method': 'set_host_enabled',
- 'args': {'enabled': 'fake_enabled'},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
-
- def test_get_host_uptime(self):
- ctxt = context.RequestContext('fake', 'fake')
- call_info = {}
- self._rpc_call_stub(call_info)
-
- self._pretend_fake_host_exists(ctxt)
- self.host_api.get_host_uptime(ctxt, 'fake_host')
- self.assertEqual(call_info['context'], ctxt)
- self.assertEqual(call_info['topic'], 'compute.fake_host')
- self.assertEqual(call_info['msg'],
- {'method': 'get_host_uptime',
- 'args': {},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
-
- def test_host_power_action(self):
- ctxt = context.get_admin_context()
- call_info = {}
- self._rpc_call_stub(call_info)
- self._pretend_fake_host_exists(ctxt)
- self.host_api.host_power_action(ctxt, 'fake_host', 'fake_action')
- self.assertEqual(call_info['context'], ctxt)
- self.assertEqual(call_info['topic'], 'compute.fake_host')
- self.assertEqual(call_info['msg'],
- {'method': 'host_power_action',
- 'args': {'action': 'fake_action'},
- 'version':
- compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
-
- def test_set_host_maintenance(self):
- ctxt = context.get_admin_context()
- call_info = {}
- self._rpc_call_stub(call_info)
- self._pretend_fake_host_exists(ctxt)
- self.host_api.set_host_maintenance(ctxt, 'fake_host', 'fake_mode')
- self.assertEqual(call_info['context'], ctxt)
- self.assertEqual(call_info['topic'], 'compute.fake_host')
- self.assertEqual(call_info['msg'],
- {'method': 'host_maintenance_mode',
- 'args': {'host': 'fake_host', 'mode': 'fake_mode'},
- 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
-
-
class KeypairAPITestCase(BaseTestCase):
def setUp(self):
super(KeypairAPITestCase, self).setUp()
diff --git a/nova/tests/compute/test_host_api.py b/nova/tests/compute/test_host_api.py
index 0af1d6766..95d3c4926 100644
--- a/nova/tests/compute/test_host_api.py
+++ b/nova/tests/compute/test_host_api.py
@@ -13,93 +13,114 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nova.compute import api
+from nova import compute
+from nova.compute import rpcapi as compute_rpcapi
from nova import context
-from nova import db
-from nova import exception
+from nova.openstack.common import rpc
from nova import test
-from nova.tests import fake_hosts
-class HostApiTestCase(test.TestCase):
- """
- Tests 'host' subset of the compute api
- """
-
+class ComputeHostAPITestCase(test.TestCase):
def setUp(self):
- super(HostApiTestCase, self).setUp()
- self.compute_rpcapi = api.compute_rpcapi
- self.api = api.HostAPI()
+ super(ComputeHostAPITestCase, self).setUp()
+ self.host_api = compute.HostAPI()
+ self.ctxt = context.get_admin_context()
- def test_bad_host_set_enabled(self):
- """
- Tests that actions on single hosts that don't exist blow up without
- having to reach the host via rpc. Should raise HostNotFound if you
- try to update a host that is not in the DB
+ def _mock_rpc_call(self, expected_message, result=None):
+ if result is None:
+ result = 'fake-result'
+ self.mox.StubOutWithMock(rpc, 'call')
+ rpc.call(self.ctxt, 'compute.fake_host',
+ expected_message, None).AndReturn(result)
+
+ def _mock_assert_host_exists(self):
+ """Sets it so that the host API always thinks that 'fake_host'
+ exists.
"""
- self.assertRaises(exception.HostNotFound, self.api.set_host_enabled,
- context.get_admin_context(), "bogus_host_name", False)
+ self.mox.StubOutWithMock(self.host_api, '_assert_host_exists')
+ self.host_api._assert_host_exists(self.ctxt, 'fake_host')
+
+ def test_set_host_enabled(self):
+ self._mock_assert_host_exists()
+ self._mock_rpc_call(
+ {'method': 'set_host_enabled',
+ 'args': {'enabled': 'fake_enabled'},
+ 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
+
+ self.mox.ReplayAll()
+ result = self.host_api.set_host_enabled(self.ctxt, 'fake_host',
+ 'fake_enabled')
+ self.assertEqual('fake-result', result)
+
+ def test_get_host_uptime(self):
+ self._mock_assert_host_exists()
+ self._mock_rpc_call(
+ {'method': 'get_host_uptime',
+ 'args': {},
+ 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
+ self.mox.ReplayAll()
+ result = self.host_api.get_host_uptime(self.ctxt, 'fake_host')
+ self.assertEqual('fake-result', result)
+
+ def test_host_power_action(self):
+ self._mock_assert_host_exists()
+ self._mock_rpc_call(
+ {'method': 'host_power_action',
+ 'args': {'action': 'fake_action'},
+ 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
+ self.mox.ReplayAll()
+ result = self.host_api.host_power_action(self.ctxt, 'fake_host',
+ 'fake_action')
+ self.assertEqual('fake-result', result)
- def test_list_compute_hosts(self):
- ctx = context.get_admin_context()
- self.mox.StubOutWithMock(db, 'service_get_all')
- db.service_get_all(ctx, False).AndReturn(fake_hosts.SERVICES_LIST)
+ def test_set_host_maintenance(self):
+ self._mock_assert_host_exists()
+ self._mock_rpc_call(
+ {'method': 'host_maintenance_mode',
+ 'args': {'host': 'fake_host', 'mode': 'fake_mode'},
+ 'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
self.mox.ReplayAll()
- compute_hosts = self.api.list_hosts(ctx, service="compute")
+ result = self.host_api.set_host_maintenance(self.ctxt, 'fake_host',
+ 'fake_mode')
+ self.assertEqual('fake-result', result)
+
+ def test_service_get_all(self):
+ services = [dict(id=1, key1='val1', key2='val2', topic='compute',
+ host='host1'),
+ dict(id=2, key1='val2', key3='val3', topic='compute',
+ host='host2')]
+ exp_services = []
+ for service in services:
+ exp_service = {}
+ exp_service.update(availability_zone='nova', **service)
+ exp_services.append(exp_service)
+
+ self.mox.StubOutWithMock(self.host_api.db,
+ 'service_get_all')
+
+ # Test no filters
+ self.host_api.db.service_get_all(self.ctxt, False).AndReturn(
+ services)
+ self.mox.ReplayAll()
+ result = self.host_api.service_get_all(self.ctxt)
self.mox.VerifyAll()
- expected = [host for host in fake_hosts.HOST_LIST
- if host["service"] == "compute"]
- self.assertEqual(expected, compute_hosts)
+ self.assertEqual(exp_services, result)
- def test_describe_host(self):
- """
- Makes sure that describe_host returns the correct information
- given our fake input.
- """
- ctx = context.get_admin_context()
- self.mox.StubOutWithMock(db, 'service_get_by_compute_host')
- host_name = 'host_c1'
- db.service_get_by_compute_host(ctx, host_name).AndReturn(
- {'host': 'fake_host',
- 'compute_node': [
- {'vcpus': 4,
- 'vcpus_used': 1,
- 'memory_mb': 8192,
- 'memory_mb_used': 2048,
- 'local_gb': 1024,
- 'local_gb_used': 648}
- ]
- })
- self.mox.StubOutWithMock(db, 'instance_get_all_by_host')
- db.instance_get_all_by_host(ctx, 'fake_host').AndReturn(
- [{'project_id': 42,
- 'vcpus': 1,
- 'memory_mb': 2048,
- 'root_gb': 648,
- 'ephemeral_gb': 0,
- }])
+ # Test no filters #2
+ self.mox.ResetAll()
+ self.host_api.db.service_get_all(self.ctxt, False).AndReturn(
+ services)
+ self.mox.ReplayAll()
+ result = self.host_api.service_get_all(self.ctxt, filters={})
+ self.mox.VerifyAll()
+ self.assertEqual(exp_services, result)
+
+ # Test w/ filter
+ self.mox.ResetAll()
+ self.host_api.db.service_get_all(self.ctxt, False).AndReturn(
+ services)
self.mox.ReplayAll()
- result = self.api.describe_host(ctx, host_name)
- self.assertEqual(result,
- [{'resource': {'cpu': 4,
- 'disk_gb': 1024,
- 'host': 'host_c1',
- 'memory_mb': 8192,
- 'project': '(total)'}},
- {'resource': {'cpu': 1,
- 'disk_gb': 648,
- 'host': 'host_c1',
- 'memory_mb': 2048,
- 'project': '(used_now)'}},
- {'resource': {'cpu': 1,
- 'disk_gb': 648,
- 'host': 'host_c1',
- 'memory_mb': 2048,
- 'project': '(used_max)'}},
- {'resource': {'cpu': 1,
- 'disk_gb': 648,
- 'host': 'host_c1',
- 'memory_mb': 2048,
- 'project': 42}}]
- )
+ result = self.host_api.service_get_all(self.ctxt,
+ filters=dict(key1='val2'))
self.mox.VerifyAll()
+ self.assertEqual([exp_services[1]], result)