From: Brett Smith Date: Thu, 26 Mar 2015 17:31:01 +0000 (-0400) Subject: 5502: Node Manager attaches a local SSD to GCE compute nodes. X-Git-Tag: 1.1.0~1715^2 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/5dfdd042f8931fa7f001256471c0b624f768504c 5502: Node Manager attaches a local SSD to GCE compute nodes. This is the best option to provide temporary working space for compute work on GCE. --- diff --git a/services/nodemanager/arvnodeman/computenode/driver/gce.py b/services/nodemanager/arvnodeman/computenode/driver/gce.py index 6380d0e342..36bfc96213 100644 --- a/services/nodemanager/arvnodeman/computenode/driver/gce.py +++ b/services/nodemanager/arvnodeman/computenode/driver/gce.py @@ -34,11 +34,17 @@ class ComputeNodeDriver(BaseComputeNodeDriver): super(ComputeNodeDriver, self).__init__( auth_kwargs, list_kwargs, create_kwargs, driver_class) + self._disktype_links = {dt.name: self._object_link(dt) + for dt in self.real.ex_list_disktypes()} @staticmethod def _name_key(cloud_object): return cloud_object.name + @staticmethod + def _object_link(cloud_object): + return cloud_object.extra.get('selfLink') + def _init_image(self, image_name): return 'image', self.search_for( image_name, 'list_images', self._name_key) @@ -59,14 +65,39 @@ class ComputeNodeDriver(BaseComputeNodeDriver): def arvados_create_kwargs(self, arvados_node): cluster_id, _, node_id = arvados_node['uuid'].split('-') - result = {'name': 'compute-{}-{}'.format(node_id, cluster_id), + name = 'compute-{}-{}'.format(node_id, cluster_id) + disks = [ + {'autoDelete': True, + 'boot': True, + 'deviceName': name, + 'initializeParams': + {'diskName': name, + 'diskType': self._disktype_links['pd-standard'], + 'sourceImage': self._object_link(self.create_kwargs['image']), + }, + 'type': 'PERSISTENT', + }, + {'autoDelete': True, + 'boot': False, + # Boot images rely on this device name to find the SSD. + # Any change must be coordinated in the image. + 'deviceName': 'tmp', + 'initializeParams': + {'diskType': self._disktype_links['local-ssd'], + }, + 'type': 'SCRATCH', + }, + ] + result = {'name': name, 'ex_metadata': self.create_kwargs['ex_metadata'].copy(), - 'ex_tags': list(self.node_tags)} - result['ex_metadata']['arv-ping-url'] = self._make_ping_url( - arvados_node) - result['ex_metadata']['booted_at'] = time.strftime(ARVADOS_TIMEFMT, - time.gmtime()) - result['ex_metadata']['hostname'] = arvados_node_fqdn(arvados_node) + 'ex_tags': list(self.node_tags), + 'ex_disks_gce_struct': disks, + } + result['ex_metadata'].update({ + 'arv-ping-url': self._make_ping_url(arvados_node), + 'booted_at': time.strftime(ARVADOS_TIMEFMT, time.gmtime()), + 'hostname': arvados_node_fqdn(arvados_node), + }) return result def list_nodes(self): diff --git a/services/nodemanager/tests/test_computenode_driver_gce.py b/services/nodemanager/tests/test_computenode_driver_gce.py index dd0d5c686a..b9d7ee9fd0 100644 --- a/services/nodemanager/tests/test_computenode_driver_gce.py +++ b/services/nodemanager/tests/test_computenode_driver_gce.py @@ -14,6 +14,20 @@ from . import testutil class GCEComputeNodeDriverTestCase(testutil.DriverTestMixin, unittest.TestCase): TEST_CLASS = gce.ComputeNodeDriver + def setUp(self): + super(GCEComputeNodeDriverTestCase, self).setUp() + self.driver_mock().list_images.return_value = [ + testutil.cloud_object_mock('testimage', selfLink='image-link')] + self.driver_mock().ex_list_disktypes.return_value = [ + testutil.cloud_object_mock(name, selfLink=name + '-link') + for name in ['pd-standard', 'pd-ssd', 'local-ssd']] + self.driver_mock.reset_mock() + + def new_driver(self, auth_kwargs={}, list_kwargs={}, create_kwargs={}): + create_kwargs.setdefault('image', 'testimage') + return super(GCEComputeNodeDriverTestCase, self).new_driver( + auth_kwargs, list_kwargs, create_kwargs) + def test_driver_instantiation(self): kwargs = {'user_id': 'foo'} driver = self.new_driver(auth_kwargs=kwargs) @@ -50,6 +64,26 @@ class GCEComputeNodeDriverTestCase(testutil.DriverTestMixin, unittest.TestCase): self.assertEqual(['testA', 'testB'], self.driver_mock().create_node.call_args[1]['ex_tags']) + def test_create_with_two_disks_attached(self): + driver = self.new_driver(create_kwargs={'image': 'testimage'}) + driver.create_node(testutil.MockSize(1), testutil.arvados_node_mock()) + create_disks = self.driver_mock().create_node.call_args[1].get( + 'ex_disks_gce_struct', []) + self.assertEqual(2, len(create_disks)) + self.assertTrue(create_disks[0].get('autoDelete')) + self.assertTrue(create_disks[0].get('boot')) + self.assertEqual('PERSISTENT', create_disks[0].get('type')) + init_params = create_disks[0].get('initializeParams', {}) + self.assertEqual('pd-standard-link', init_params.get('diskType')) + self.assertEqual('image-link', init_params.get('sourceImage')) + # Our node images expect the SSD to be named `tmp` to find and mount it. + self.assertEqual('tmp', create_disks[1].get('deviceName')) + self.assertTrue(create_disks[1].get('autoDelete')) + self.assertFalse(create_disks[1].get('boot', 'unset')) + self.assertEqual('SCRATCH', create_disks[1].get('type')) + init_params = create_disks[1].get('initializeParams', {}) + self.assertEqual('local-ssd-link', init_params.get('diskType')) + def test_list_nodes_requires_tags_match(self): # A node matches if our list tags are a subset of the node's tags. # Test behavior with no tags, no match, partial matches, different diff --git a/services/nodemanager/tests/testutil.py b/services/nodemanager/tests/testutil.py index 6c386e25f5..82d6479e24 100644 --- a/services/nodemanager/tests/testutil.py +++ b/services/nodemanager/tests/testutil.py @@ -34,13 +34,14 @@ def arvados_node_mock(node_num=99, job_uuid=None, age=-1, **kwargs): node.update(kwargs) return node -def cloud_object_mock(name_id): +def cloud_object_mock(name_id, **extra): # A very generic mock, useful for stubbing libcloud objects we # only search for and pass around, like locations, subnets, etc. cloud_object = mock.NonCallableMagicMock(['id', 'name'], name='cloud_object') cloud_object.name = str(name_id) cloud_object.id = 'id_' + cloud_object.name + cloud_object.extra = extra return cloud_object def cloud_node_mock(node_num=99, **extra):