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)
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):
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)
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
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):