Adopt operation for nova server profile
This adds the operation support for adopting an existing server. There are still some items we cannot get from nova server, due to Nova API limitations, but the current support should be okay for most use cases if users don't care much about 'user_data'. Change-Id: Iaca7a5f5ab8cc4aad6707f5a1148457874d415ea
This commit is contained in:
parent
ad13f9fa70
commit
9dea560572
|
@ -1048,6 +1048,86 @@ class ServerProfile(base.Profile):
|
|||
|
||||
return dict((k, details[k]) for k in sorted(details))
|
||||
|
||||
def do_adopt(self, obj, overrides=None, snapshot=False):
|
||||
"""Adopt an existing server node for management.
|
||||
|
||||
:param obj: A node object for this operation. It could be a puppet
|
||||
node that provides only 'user', 'project' and 'physical_id'
|
||||
properties when doing a preview. It can be a real Node object for
|
||||
node adoption.
|
||||
:param overrides: A dict containing the properties that will be
|
||||
overridden when generating a profile for the server.
|
||||
:param snapshot: A boolean flag indicating whether the profile should
|
||||
attempt a snapshot operation before adopting the server. If set to
|
||||
True, the ID of the snapshot will be used as the image ID.
|
||||
|
||||
:returns: A dict containing the spec created from the server object or
|
||||
a dict containing error information if failure occurred.
|
||||
"""
|
||||
driver = self.compute(obj)
|
||||
|
||||
# TODO(Qiming): Add snapshot support
|
||||
# snapshot = driver.snapshot_create(...)
|
||||
|
||||
error = {}
|
||||
try:
|
||||
server = driver.server_get(obj.physical_id)
|
||||
except exc.InternalError as ex:
|
||||
error = {'code': ex.code, 'message': six.text_type(ex)}
|
||||
|
||||
if error:
|
||||
return {'Error': error}
|
||||
|
||||
spec = {}
|
||||
# Context?
|
||||
# TODO(Qiming): Need to fetch admin password from a different API
|
||||
disk_config = server.disk_config
|
||||
spec[self.AUTO_DISK_CONFIG] = disk_config and disk_config == 'AUTO'
|
||||
|
||||
spec[self.AVAILABILITY_ZONE] = server.availability_zone
|
||||
|
||||
# TODO(Anyone): verify if this needs a format conversion
|
||||
bdm = server.block_device_mapping
|
||||
spec[self.BLOCK_DEVICE_MAPPING_V2] = bdm
|
||||
|
||||
spec[self.CONFIG_DRIVE] = server.has_config_drive
|
||||
spec[self.FLAVOR] = server.flavor['id']
|
||||
spec[self.IMAGE] = server.image['id'] or server.image
|
||||
spec[self.KEY_NAME] = server.key_name
|
||||
|
||||
# metadata
|
||||
metadata = server.metadata or {}
|
||||
metadata.pop('cluster_id', None)
|
||||
metadata.pop('cluster_node_id', None)
|
||||
metadata.pop('cluster_node_index', None)
|
||||
spec[self.METADATA] = metadata
|
||||
|
||||
# name
|
||||
spec[self.NAME] = server.name
|
||||
|
||||
networks = server.addresses
|
||||
net_list = []
|
||||
for network, interfaces in networks.items():
|
||||
for intf in interfaces:
|
||||
ip_type = intf.get('OS-EXT-IPS:type')
|
||||
if ip_type == 'fixed':
|
||||
net_list.append({self.NETWORK: network})
|
||||
|
||||
spec[self.NETWORKS] = net_list
|
||||
# NOTE: the personality attribute is missing for ever.
|
||||
spec[self.SECURITY_GROUPS] = [
|
||||
sg['name'] for sg in server.security_groups
|
||||
]
|
||||
# TODO(Qiming): get server user_data and parse it.
|
||||
# Note: user_data is returned in 2.3 microversion API, in a different
|
||||
# property name.
|
||||
# spec[self.USER_DATA] = server.user_data
|
||||
|
||||
if overrides:
|
||||
spec.update(overrides)
|
||||
|
||||
return spec
|
||||
|
||||
def do_join(self, obj, cluster_id):
|
||||
if not obj.physical_id:
|
||||
return False
|
||||
|
|
|
@ -859,28 +859,126 @@ class TestNovaServerBasic(base.SenlinTestCase):
|
|||
self.assertEqual(expected, res)
|
||||
cc.server_get.assert_called_once_with('FAKE_ID')
|
||||
|
||||
def test_do_join_successful(self):
|
||||
def test_do_adopt(self):
|
||||
profile = server.ServerProfile('t', self.spec)
|
||||
|
||||
cluster_id = "FAKE_CLUSTER_ID"
|
||||
x_server = mock.Mock(
|
||||
disk_config="",
|
||||
availability_zone="AZ01",
|
||||
block_device_mapping={"foo": "bar"},
|
||||
has_config_drive=False,
|
||||
flavor={"id": "FLAVOR_ID"},
|
||||
image={"id": "IMAGE_ID"},
|
||||
key_name="FAKE_KEY",
|
||||
metadata={
|
||||
"mkey": "mvalue",
|
||||
"cluster_id": "CLUSTER_ID",
|
||||
"cluster_node_id": "NODE_ID",
|
||||
"cluster_node_index": 123
|
||||
},
|
||||
addresses={
|
||||
"NET1": [{
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "ADDR1"
|
||||
}],
|
||||
"NET2": [{
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "ADDR2"
|
||||
}],
|
||||
},
|
||||
security_groups=[{'name': 'GROUP1'}, {'name': 'GROUP2'}]
|
||||
)
|
||||
x_server.name = "FAKE_NAME"
|
||||
cc = mock.Mock()
|
||||
cc.server_metadata_get.return_value = {'FOO': 'BAR'}
|
||||
cc.server_metadata_update.return_value = {'cluster_id': cluster_id}
|
||||
cc.server_get.return_value = x_server
|
||||
profile._computeclient = cc
|
||||
node_obj = mock.Mock(physical_id='FAKE_ID')
|
||||
|
||||
node_obj = mock.Mock(physical_id='FAKE_ID', index=567)
|
||||
res = profile.do_adopt(node_obj)
|
||||
|
||||
res = profile.do_join(node_obj, cluster_id)
|
||||
self.assertEqual('', res['auto_disk_config'])
|
||||
self.assertEqual('AZ01', res['availability_zone'])
|
||||
self.assertEqual({'foo': 'bar'}, res['block_device_mapping_v2'])
|
||||
self.assertFalse(res['config_drive'])
|
||||
self.assertEqual('FLAVOR_ID', res['flavor'])
|
||||
self.assertEqual('IMAGE_ID', res['image'])
|
||||
self.assertEqual('FAKE_KEY', res['key_name'])
|
||||
self.assertEqual({'mkey': 'mvalue'}, res['metadata'])
|
||||
self.assertIn({'network': 'NET1'}, res['networks'])
|
||||
self.assertIn({'network': 'NET2'}, res['networks'])
|
||||
self.assertIn('GROUP1', res['security_groups'])
|
||||
self.assertIn('GROUP2', res['security_groups'])
|
||||
cc.server_get.assert_called_once_with('FAKE_ID')
|
||||
|
||||
self.assertTrue(res)
|
||||
cc.server_metadata_get.assert_called_once_with('FAKE_ID')
|
||||
expected_metadata = {
|
||||
'cluster_id': 'FAKE_CLUSTER_ID',
|
||||
'cluster_node_index': '567',
|
||||
'FOO': 'BAR'
|
||||
def test_do_adopt_failed_get(self):
|
||||
profile = server.ServerProfile('t', self.spec)
|
||||
cc = mock.Mock()
|
||||
err = exc.InternalError(code=404, message='No Server found for ID')
|
||||
cc.server_get.side_effect = err
|
||||
profile._computeclient = cc
|
||||
node_obj = mock.Mock(physical_id='FAKE_ID')
|
||||
|
||||
res = profile.do_adopt(node_obj)
|
||||
|
||||
expected = {
|
||||
'Error': {
|
||||
'code': 404,
|
||||
'message': 'No Server found for ID',
|
||||
}
|
||||
}
|
||||
cc.server_metadata_update.assert_called_once_with(
|
||||
'FAKE_ID', expected_metadata)
|
||||
self.assertEqual(expected, res)
|
||||
cc.server_get.assert_called_once_with('FAKE_ID')
|
||||
|
||||
def test_do_adopt_with_overrides(self):
|
||||
profile = server.ServerProfile('t', self.spec)
|
||||
x_server = mock.Mock(
|
||||
disk_config="",
|
||||
availability_zone="AZ01",
|
||||
block_device_mapping={"foo": "bar"},
|
||||
has_config_drive=False,
|
||||
flavor={"id": "FLAVOR_ID"},
|
||||
image={"id": "IMAGE_ID"},
|
||||
key_name="FAKE_KEY",
|
||||
metadata={
|
||||
"mkey": "mvalue",
|
||||
"cluster_id": "CLUSTER_ID",
|
||||
"cluster_node_id": "NODE_ID",
|
||||
"cluster_node_index": 123
|
||||
},
|
||||
addresses={
|
||||
"NET1": [{
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
}],
|
||||
"NET2": [{
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
}],
|
||||
},
|
||||
security_groups=[{'name': 'GROUP1'}, {'name': 'GROUP2'}]
|
||||
)
|
||||
x_server.name = "FAKE_NAME"
|
||||
cc = mock.Mock()
|
||||
cc.server_get.return_value = x_server
|
||||
profile._computeclient = cc
|
||||
node_obj = mock.Mock(physical_id='FAKE_ID')
|
||||
overrides = {
|
||||
'networks': [{"network": "NET3"}]
|
||||
}
|
||||
|
||||
res = profile.do_adopt(node_obj, overrides=overrides)
|
||||
|
||||
self.assertEqual('', res['auto_disk_config'])
|
||||
self.assertEqual('AZ01', res['availability_zone'])
|
||||
self.assertEqual({'foo': 'bar'}, res['block_device_mapping_v2'])
|
||||
self.assertFalse(res['config_drive'])
|
||||
self.assertEqual('FLAVOR_ID', res['flavor'])
|
||||
self.assertEqual('IMAGE_ID', res['image'])
|
||||
self.assertEqual('FAKE_KEY', res['key_name'])
|
||||
self.assertEqual({'mkey': 'mvalue'}, res['metadata'])
|
||||
self.assertIn({'network': 'NET3'}, res['networks'])
|
||||
self.assertNotIn({'network': 'NET1'}, res['networks'])
|
||||
self.assertNotIn({'network': 'NET2'}, res['networks'])
|
||||
self.assertIn('GROUP1', res['security_groups'])
|
||||
self.assertIn('GROUP2', res['security_groups'])
|
||||
cc.server_get.assert_called_once_with('FAKE_ID')
|
||||
|
||||
def test_do_join_server_not_created(self):
|
||||
# Test path where server not specified
|
||||
|
|
Loading…
Reference in New Issue