Introduce a function to parse image device mappings
Images have two kinds of device mappings attribute: virtual (mappings) and block (block_device_mapping). The last one may be stored in two different formats (legacy and bdm v2). Device descriptions are merged from this two attributes to a single list used for instance boot operation. We should have a function which parses and merges image device mappings and returns the result device list of a certain format to use it in various parts of ec2api. This patch introduce the function which implements the requirements above and returns device mappings in bdm v2 format. Change-Id: I7ad8673dcb6ddba11c6e00e22aa68a35ea1ec0fd
This commit is contained in:
parent
516d4a1ce5
commit
9be22b8ac8
|
@ -12,7 +12,9 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
# TODO(termie): replace minidom with etree
|
||||
from xml.dom import minidom
|
||||
|
@ -42,6 +44,9 @@ ec2_opts = [
|
|||
CONF = cfg.CONF
|
||||
CONF.register_opts(ec2_opts)
|
||||
|
||||
LEGACY_BDM_FIELDS = set(['device_name', 'delete_on_termination', 'snapshot_id',
|
||||
'volume_id', 'volume_size', 'no_device'])
|
||||
|
||||
_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
|
||||
|
||||
|
||||
|
@ -385,6 +390,89 @@ def get_os_image(context, ec2_image_id):
|
|||
raise exception.InvalidAMIIDNotFound(id=ec2_image_id)
|
||||
|
||||
|
||||
def deserialize_os_image_properties(os_image):
|
||||
def prepare_property(property_name):
|
||||
if property_name in properties:
|
||||
properties[property_name] = json.loads(properties[property_name])
|
||||
|
||||
properties = copy.copy(os_image.properties)
|
||||
prepare_property('mappings')
|
||||
prepare_property('block_device_mapping')
|
||||
return properties
|
||||
|
||||
|
||||
def create_virtual_bdm(device_name, virtual_name):
|
||||
bdm = {'device_name': device_name,
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': virtual_name}
|
||||
if virtual_name == 'swap':
|
||||
bdm['guest_format'] = 'swap'
|
||||
return bdm
|
||||
|
||||
|
||||
def get_os_image_mappings(os_image_properties):
|
||||
mappings = []
|
||||
names = set()
|
||||
# TODO(ft): validate device names for both virtual and block device
|
||||
# mappings
|
||||
|
||||
def is_virtual(virtual_name):
|
||||
return virtual_name == 'swap' or (virtual_name and
|
||||
_ephemeral.match(virtual_name))
|
||||
|
||||
# NOTE(ft): substitute mapping if only device name is specified
|
||||
def add_mapping(mapping):
|
||||
device_name = block_device_strip_dev(mapping.get('device_name'))
|
||||
if device_name in names:
|
||||
for i, m in enumerate(mappings):
|
||||
if (device_name ==
|
||||
block_device_strip_dev(m.get('device_name'))):
|
||||
mappings[i] = mapping
|
||||
break
|
||||
else:
|
||||
if device_name:
|
||||
names.add(device_name)
|
||||
mappings.append(mapping)
|
||||
|
||||
# TODO(ft): From Juno virtual device mapping has precedence of block one
|
||||
# in boot logic. This function should do the same, despite Nova EC2
|
||||
# behavior.
|
||||
|
||||
# TODO(ft): Nova EC2 prepended device names for virtual device mappings.
|
||||
# But AWS doesn't do it.
|
||||
for vdm in os_image_properties.get('mappings', []):
|
||||
if is_virtual(vdm.get('virtual')):
|
||||
add_mapping(create_virtual_bdm(
|
||||
block_device_prepend_dev(vdm.get('device')), vdm['virtual']))
|
||||
|
||||
legacy_mapping = not os_image_properties.get('bdm_v2', False)
|
||||
for bdm in os_image_properties.get('block_device_mapping', []):
|
||||
if legacy_mapping:
|
||||
virtual_name = bdm.get('virtual_name')
|
||||
if is_virtual(virtual_name):
|
||||
new_bdm = create_virtual_bdm(bdm.get('device_name'),
|
||||
virtual_name)
|
||||
else:
|
||||
new_bdm = {key: val for key, val in bdm.iteritems()
|
||||
if key in LEGACY_BDM_FIELDS}
|
||||
if bdm.get('snapshot_id'):
|
||||
new_bdm.update({'source_type': 'snapshot',
|
||||
'destination_type': 'volume'})
|
||||
elif bdm.get('volume_id'):
|
||||
new_bdm.update({'source_type': 'volume',
|
||||
'destination_type': 'volume'})
|
||||
bdm = new_bdm
|
||||
|
||||
bdm.setdefault('delete_on_termination', False)
|
||||
add_mapping(bdm)
|
||||
|
||||
return mappings
|
||||
|
||||
|
||||
def get_os_public_network(context):
|
||||
neutron = clients.neutron(context)
|
||||
search_opts = {'router:external': True, 'name': CONF.external_network}
|
||||
|
@ -415,6 +503,8 @@ def get_attached_gateway(context, vpc_id, gateway_kind):
|
|||
|
||||
# NOTE(ft): following functions are copied from various parts of Nova
|
||||
|
||||
_ephemeral = re.compile('^ephemeral(\d|[1-9]\d+)$')
|
||||
|
||||
_dev = re.compile('^/dev/')
|
||||
|
||||
|
||||
|
|
|
@ -315,6 +315,272 @@ class EC2UtilsTestCase(testtools.TestCase):
|
|||
self.assertNotEqual(0, len(log.output))
|
||||
self.assertNotIn('None', log.output)
|
||||
|
||||
def test_get_os_image_mappings(self):
|
||||
# check virtual device mapping transformation with substitution
|
||||
properties = {
|
||||
'mappings': [
|
||||
{'device': '/dev/vda', 'virtual': 'root'},
|
||||
{'device': 'vda', 'virtual': 'ami'},
|
||||
{'device': 'vdb', 'virtual': 'ephemeral0'},
|
||||
{'device': '/dev/vdb', 'virtual': 'swap'},
|
||||
{'device': '/dev/vdc', 'virtual': 'swap'},
|
||||
{'device': 'vdc', 'virtual': 'ephemeral0'},
|
||||
{'device': 'vdd'},
|
||||
{'device': '/dev/vdd', 'virtual': None},
|
||||
{'device': 'vdd', 'virtual': ''},
|
||||
{'device': '/dev/vdd', 'virtual': 'swamp'},
|
||||
{'virtual': 'ephemeral2'},
|
||||
{'device': None, 'virtual': 'ephemeral3'},
|
||||
{'device': '', 'virtual': 'ephemeral4'},
|
||||
],
|
||||
}
|
||||
expected = [
|
||||
{'device_name': '/dev/vdb',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'guest_format': 'swap',
|
||||
'virtual_name': 'swap'},
|
||||
{'device_name': '/dev/vdc',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': 'ephemeral0'},
|
||||
{'device_name': None,
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': 'ephemeral2'},
|
||||
{'device_name': None,
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': 'ephemeral3'},
|
||||
{'device_name': '',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': 'ephemeral4'},
|
||||
]
|
||||
result = ec2utils.get_os_image_mappings(properties)
|
||||
self.assertThat(expected, matchers.ListMatches(result), verbose=True)
|
||||
|
||||
# check legacy block device mapping transformation with substitution
|
||||
properties = {
|
||||
'block_device_mapping': [
|
||||
{'device_name': '/dev/vdb',
|
||||
'virtual_name': 'ephemeral0'},
|
||||
{'device_name': 'vdc',
|
||||
'virtual_name': 'swap',
|
||||
'snapshot_id': 'fake_snapshot_id_0'},
|
||||
{'device_name': '/dev/vda',
|
||||
'snapshot_id': 'fake_snapshot_id_1',
|
||||
'delete_on_termination': True,
|
||||
'volume_size': 100},
|
||||
{'snapshot_id': 'fake_snapshot_id_2'},
|
||||
{'device_name': '/dev/vdd',
|
||||
'virtual_name': 'ephemeral2'},
|
||||
{'device_name': 'vdd',
|
||||
'volume_id': 'fake_volume_id_3',
|
||||
'delete_on_termination': False},
|
||||
{'device_name': 'vde',
|
||||
'volume_id': 'fake_volume_id_4'},
|
||||
{'device_name': '/dev/vde',
|
||||
'snapshot_id': 'fake_snapshot_id_4',
|
||||
'no_device': True},
|
||||
{'snapshot_id': 'fake_snapshot_id_5',
|
||||
'volume_id': 'fake_volume_id_5',
|
||||
'volume_size': 50},
|
||||
],
|
||||
}
|
||||
expected = [
|
||||
{'device_name': '/dev/vdb',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': 'ephemeral0'},
|
||||
{'device_name': 'vdc',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'guest_format': 'swap',
|
||||
'virtual_name': 'swap'},
|
||||
{'device_name': '/dev/vda',
|
||||
'snapshot_id': 'fake_snapshot_id_1',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'delete_on_termination': True,
|
||||
'volume_size': 100},
|
||||
{'snapshot_id': 'fake_snapshot_id_2',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'delete_on_termination': False},
|
||||
{'device_name': 'vdd',
|
||||
'volume_id': 'fake_volume_id_3',
|
||||
'source_type': 'volume',
|
||||
'destination_type': 'volume',
|
||||
'delete_on_termination': False},
|
||||
{'device_name': '/dev/vde',
|
||||
'snapshot_id': 'fake_snapshot_id_4',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'no_device': True,
|
||||
'delete_on_termination': False},
|
||||
{'snapshot_id': 'fake_snapshot_id_5',
|
||||
'volume_id': 'fake_volume_id_5',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'volume_size': 50,
|
||||
'delete_on_termination': False},
|
||||
]
|
||||
result = ec2utils.get_os_image_mappings(properties)
|
||||
self.assertThat(expected, matchers.ListMatches(result), verbose=True)
|
||||
|
||||
# check bdm v2 with substitution
|
||||
properties = {
|
||||
'bdm_v2': True,
|
||||
'block_device_mapping': [
|
||||
{'device_name': '/dev/vdb',
|
||||
'snapshot_id': 'fake_snapshot_id_1',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'volume_size': 20,
|
||||
'delete_on_termination': True},
|
||||
{'device_name': '/dev/vdb',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'volume',
|
||||
'volume_size': 10,
|
||||
'delete_on_termination': True},
|
||||
{'device_name': '/dev/vdc',
|
||||
'snapshot_id': 'fake_snapshot_id_2',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume'},
|
||||
{'device_name': 'vdc',
|
||||
'volume_id': 'fake_volume_id_2',
|
||||
'source_type': 'volume',
|
||||
'destination_type': 'volume'},
|
||||
{'device_name': 'vdd',
|
||||
'snapshot_id': 'fake_snapshot_id_3',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume'},
|
||||
{'device_name': '/dev/vdd',
|
||||
'image_id': 'fake_image_id_1',
|
||||
'source_type': 'image',
|
||||
'destination_type': 'volume',
|
||||
'volume_size': 30},
|
||||
],
|
||||
}
|
||||
expected = [
|
||||
{'device_name': '/dev/vdb',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'volume',
|
||||
'volume_size': 10,
|
||||
'delete_on_termination': True},
|
||||
{'device_name': 'vdc',
|
||||
'volume_id': 'fake_volume_id_2',
|
||||
'source_type': 'volume',
|
||||
'destination_type': 'volume',
|
||||
'delete_on_termination': False},
|
||||
{'device_name': '/dev/vdd',
|
||||
'image_id': 'fake_image_id_1',
|
||||
'source_type': 'image',
|
||||
'destination_type': 'volume',
|
||||
'volume_size': 30,
|
||||
'delete_on_termination': False},
|
||||
]
|
||||
result = ec2utils.get_os_image_mappings(properties)
|
||||
self.assertThat(expected, matchers.ListMatches(result), verbose=True)
|
||||
|
||||
# check bdm v2 vs vdm susbtitution
|
||||
properties = {
|
||||
'mappings': [
|
||||
{'device': 'vdb', 'virtual': 'ephemeral0'},
|
||||
{'device': 'vdc', 'virtual': 'ephemeral1'},
|
||||
{'device': 'vdh', 'virtual': 'ephemeral2'},
|
||||
],
|
||||
'bdm_v2': True,
|
||||
'block_device_mapping': [
|
||||
{'device_name': '/dev/vda',
|
||||
'snapshot_id': 'fake_snapshot_id_1',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume'},
|
||||
{'device_name': '/dev/vdc',
|
||||
'snapshot_id': 'fake_snapshot_id_2',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume'},
|
||||
{'device_name': '/dev/vdd',
|
||||
'snapshot_id': 'fake_snapshot_id_3',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume'}
|
||||
],
|
||||
}
|
||||
expected = [
|
||||
{'device_name': '/dev/vdb',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': 'ephemeral0'},
|
||||
{'device_name': '/dev/vdc',
|
||||
'snapshot_id': 'fake_snapshot_id_2',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'delete_on_termination': False},
|
||||
{'device_name': '/dev/vdh',
|
||||
'source_type': 'blank',
|
||||
'destination_type': 'local',
|
||||
'device_type': 'disk',
|
||||
'delete_on_termination': True,
|
||||
'boot_index': -1,
|
||||
'virtual_name': 'ephemeral2'},
|
||||
{'device_name': '/dev/vda',
|
||||
'snapshot_id': 'fake_snapshot_id_1',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'delete_on_termination': False},
|
||||
{'device_name': '/dev/vdd',
|
||||
'snapshot_id': 'fake_snapshot_id_3',
|
||||
'source_type': 'snapshot',
|
||||
'destination_type': 'volume',
|
||||
'delete_on_termination': False},
|
||||
]
|
||||
result = ec2utils.get_os_image_mappings(properties)
|
||||
self.assertThat(expected, matchers.ListMatches(result), verbose=True)
|
||||
|
||||
# check legacy bdm vs vdm susbtitution
|
||||
properties = {
|
||||
'mappings': [
|
||||
{'device': 'vdb', 'virtual': 'ephemeral0'},
|
||||
{'device': 'vdc', 'virtual': 'ephemeral1'},
|
||||
{'device': 'vdh', 'virtual': 'ephemeral2'},
|
||||
],
|
||||
'block_device_mapping': [
|
||||
{'device_name': '/dev/vda',
|
||||
'snapshot_id': 'fake_snapshot_id_1'},
|
||||
{'device_name': '/dev/vdc',
|
||||
'snapshot_id': 'fake_snapshot_id_2'},
|
||||
{'device_name': '/dev/vdd',
|
||||
'snapshot_id': 'fake_snapshot_id_3'}
|
||||
],
|
||||
}
|
||||
result = ec2utils.get_os_image_mappings(properties)
|
||||
self.assertThat(expected, matchers.ListMatches(result), verbose=True)
|
||||
|
||||
def test_block_device_strip_dev(self):
|
||||
self.assertEqual(ec2utils.block_device_strip_dev('/dev/sda'), 'sda')
|
||||
self.assertEqual(ec2utils.block_device_strip_dev('sda'), 'sda')
|
||||
|
|
Loading…
Reference in New Issue