@@ -678,6 +678,51 @@ def __call__(self, parser, namespace, values, option_string=None):
678678 getattr (namespace , self .dest ).append (info )
679679
680680
681+ class BDMLegacyAction (argparse .Action ):
682+
683+ def __call__ (self , parser , namespace , values , option_string = None ):
684+ # Make sure we have an empty dict rather than None
685+ if getattr (namespace , self .dest , None ) is None :
686+ setattr (namespace , self .dest , [])
687+
688+ dev_name , sep , dev_map = values .partition ('=' )
689+ dev_map = dev_map .split (':' ) if dev_map else dev_map
690+ if not dev_name or not dev_map or len (dev_map ) > 4 :
691+ msg = _ (
692+ "Invalid argument %s; argument must be of form "
693+ "'dev-name=id[:type[:size[:delete-on-terminate]]]'"
694+ )
695+ raise argparse .ArgumentTypeError (msg % values )
696+
697+ mapping = {
698+ 'device_name' : dev_name ,
699+ # store target; this may be a name and will need verification later
700+ 'uuid' : dev_map [0 ],
701+ 'source_type' : 'volume' ,
702+ 'destination_type' : 'volume' ,
703+ }
704+
705+ # decide source and destination type
706+ if len (dev_map ) > 1 and dev_map [1 ]:
707+ if dev_map [1 ] not in ('volume' , 'snapshot' , 'image' ):
708+ msg = _ (
709+ "Invalid argument %s; 'type' must be one of: volume, "
710+ "snapshot, image"
711+ )
712+ raise argparse .ArgumentTypeError (msg % values )
713+
714+ mapping ['source_type' ] = dev_map [1 ]
715+
716+ # 3. append size and delete_on_termination, if present
717+ if len (dev_map ) > 2 and dev_map [2 ]:
718+ mapping ['volume_size' ] = dev_map [2 ]
719+
720+ if len (dev_map ) > 3 and dev_map [3 ]:
721+ mapping ['delete_on_termination' ] = dev_map [3 ]
722+
723+ getattr (namespace , self .dest ).append (mapping )
724+
725+
681726class CreateServer (command .ShowOne ):
682727 _description = _ ("Create a new server" )
683728
@@ -745,8 +790,8 @@ def get_parser(self, prog_name):
745790 parser .add_argument (
746791 '--block-device-mapping' ,
747792 metavar = '<dev-name=mapping>' ,
748- action = parseractions . KeyValueAction ,
749- default = {} ,
793+ action = BDMLegacyAction ,
794+ default = [] ,
750795 # NOTE(RuiChen): Add '\n' to the end of line to improve formatting;
751796 # see cliff's _SmartHelpFormatter for more details.
752797 help = _ (
@@ -1123,62 +1168,35 @@ def _match_image(image_api, wanted_properties):
11231168 # If booting from volume we do not pass an image to compute.
11241169 image = None
11251170
1126- boot_args = [parsed_args .server_name , image , flavor ]
1127-
11281171 # Handle block device by device name order, like: vdb -> vdc -> vdd
1129- for dev_name in sorted (parsed_args .block_device_mapping ):
1130- dev_map = parsed_args .block_device_mapping [dev_name ]
1131- dev_map = dev_map .split (':' )
1132- if dev_map [0 ]:
1133- mapping = {'device_name' : dev_name }
1134-
1135- # 1. decide source and destination type
1136- if (len (dev_map ) > 1 and
1137- dev_map [1 ] in ('volume' , 'snapshot' , 'image' )):
1138- mapping ['source_type' ] = dev_map [1 ]
1139- else :
1140- mapping ['source_type' ] = 'volume'
1141-
1142- mapping ['destination_type' ] = 'volume'
1143-
1144- # 2. check target exist, update target uuid according by
1145- # source type
1146- if mapping ['source_type' ] == 'volume' :
1147- volume_id = utils .find_resource (
1148- volume_client .volumes , dev_map [0 ]).id
1149- mapping ['uuid' ] = volume_id
1150- elif mapping ['source_type' ] == 'snapshot' :
1151- snapshot_id = utils .find_resource (
1152- volume_client .volume_snapshots , dev_map [0 ]).id
1153- mapping ['uuid' ] = snapshot_id
1154- elif mapping ['source_type' ] == 'image' :
1155- # NOTE(mriedem): In case --image is specified with the same
1156- # image, that becomes the root disk for the server. If the
1157- # block device is specified with a root device name, e.g.
1158- # vda, then the compute API will likely fail complaining
1159- # that there is a conflict. So if using the same image ID,
1160- # which doesn't really make sense but it's allowed, the
1161- # device name would need to be a non-root device, e.g. vdb.
1162- # Otherwise if the block device image is different from the
1163- # one specified by --image, then the compute service will
1164- # create a volume from the image and attach it to the
1165- # server as a non-root volume.
1166- image_id = image_client .find_image (dev_map [0 ],
1167- ignore_missing = False ).id
1168- mapping ['uuid' ] = image_id
1169-
1170- # 3. append size and delete_on_termination if exist
1171- if len (dev_map ) > 2 and dev_map [2 ]:
1172- mapping ['volume_size' ] = dev_map [2 ]
1173-
1174- if len (dev_map ) > 3 and dev_map [3 ]:
1175- mapping ['delete_on_termination' ] = dev_map [3 ]
1176- else :
1177- msg = _ (
1178- 'Volume, volume snapshot or image (name or ID) must '
1179- 'be specified if --block-device-mapping is specified'
1180- )
1181- raise exceptions .CommandError (msg )
1172+ for mapping in parsed_args .block_device_mapping :
1173+ if mapping ['source_type' ] == 'volume' :
1174+ volume_id = utils .find_resource (
1175+ volume_client .volumes , mapping ['uuid' ],
1176+ ).id
1177+ mapping ['uuid' ] = volume_id
1178+ elif mapping ['source_type' ] == 'snapshot' :
1179+ snapshot_id = utils .find_resource (
1180+ volume_client .volume_snapshots , mapping ['uuid' ],
1181+ ).id
1182+ mapping ['uuid' ] = snapshot_id
1183+ elif mapping ['source_type' ] == 'image' :
1184+ # NOTE(mriedem): In case --image is specified with the same
1185+ # image, that becomes the root disk for the server. If the
1186+ # block device is specified with a root device name, e.g.
1187+ # vda, then the compute API will likely fail complaining
1188+ # that there is a conflict. So if using the same image ID,
1189+ # which doesn't really make sense but it's allowed, the
1190+ # device name would need to be a non-root device, e.g. vdb.
1191+ # Otherwise if the block device image is different from the
1192+ # one specified by --image, then the compute service will
1193+ # create a volume from the image and attach it to the
1194+ # server as a non-root volume.
1195+ image_id = image_client .find_image (
1196+ mapping ['uuid' ], ignore_missing = False ,
1197+ ).id
1198+ mapping ['uuid' ] = image_id
1199+
11821200 block_device_mapping_v2 .append (mapping )
11831201
11841202 nics = parsed_args .nics
@@ -1281,6 +1299,8 @@ def _match_image(image_api, wanted_properties):
12811299 else :
12821300 config_drive = parsed_args .config_drive
12831301
1302+ boot_args = [parsed_args .server_name , image , flavor ]
1303+
12841304 boot_kwargs = dict (
12851305 meta = parsed_args .properties ,
12861306 files = files ,
0 commit comments