Merge "trivial: Rework 'CreateServer' function"

This commit is contained in:
Zuul 2020-10-30 17:30:34 +00:00 committed by Gerrit Code Review
commit 987af4e390

View File

@ -640,13 +640,15 @@ class CreateServer(command.ShowOne):
disk_group.add_argument( disk_group.add_argument(
'--volume', '--volume',
metavar='<volume>', metavar='<volume>',
help=_('Create server using this volume as the boot disk (name ' help=_(
'or ID).\n' 'Create server using this volume as the boot disk (name or ID)'
'This option automatically creates a block device mapping ' '\n'
'with a boot index of 0. On many hypervisors (libvirt/kvm ' 'This option automatically creates a block device mapping '
'for example) this will be device vda. Do not create a ' 'with a boot index of 0. On many hypervisors (libvirt/kvm '
'duplicate mapping using --block-device-mapping for this ' 'for example) this will be device vda. Do not create a '
'volume.'), 'duplicate mapping using --block-device-mapping for this '
'volume.'
),
) )
parser.add_argument( parser.add_argument(
'--password', '--password',
@ -664,28 +666,34 @@ class CreateServer(command.ShowOne):
metavar='<security-group>', metavar='<security-group>',
action='append', action='append',
default=[], default=[],
help=_('Security group to assign to this server (name or ID) ' help=_(
'(repeat option to set multiple groups)'), 'Security group to assign to this server (name or ID) '
'(repeat option to set multiple groups)'
),
) )
parser.add_argument( parser.add_argument(
'--key-name', '--key-name',
metavar='<key-name>', metavar='<key-name>',
help=_('Keypair to inject into this server (optional extension)'), help=_('Keypair to inject into this server'),
) )
parser.add_argument( parser.add_argument(
'--property', '--property',
metavar='<key=value>', metavar='<key=value>',
action=parseractions.KeyValueAction, action=parseractions.KeyValueAction,
help=_('Set a property on this server ' help=_(
'(repeat option to set multiple values)'), 'Set a property on this server '
'(repeat option to set multiple values)'
),
) )
parser.add_argument( parser.add_argument(
'--file', '--file',
metavar='<dest-filename=source-filename>', metavar='<dest-filename=source-filename>',
action='append', action='append',
default=[], default=[],
help=_('File to inject into image before boot ' help=_(
'(repeat option to set multiple files)'), 'File to inject into image before boot '
'(repeat option to set multiple files)'
),
) )
parser.add_argument( parser.add_argument(
'--user-data', '--user-data',
@ -695,8 +703,10 @@ class CreateServer(command.ShowOne):
parser.add_argument( parser.add_argument(
'--description', '--description',
metavar='<description>', metavar='<description>',
help=_('Set description for the server (supported by ' help=_(
'--os-compute-api-version 2.19 or above)'), 'Set description for the server '
'(supported by --os-compute-api-version 2.19 or above)'
),
) )
parser.add_argument( parser.add_argument(
'--availability-zone', '--availability-zone',
@ -706,29 +716,35 @@ class CreateServer(command.ShowOne):
parser.add_argument( parser.add_argument(
'--host', '--host',
metavar='<host>', metavar='<host>',
help=_('Requested host to create servers. Admin only ' help=_(
'by default. (supported by --os-compute-api-version 2.74 ' 'Requested host to create servers. '
'or above)'), '(admin only) '
'(supported by --os-compute-api-version 2.74 or above)'
),
) )
parser.add_argument( parser.add_argument(
'--hypervisor-hostname', '--hypervisor-hostname',
metavar='<hypervisor-hostname>', metavar='<hypervisor-hostname>',
help=_('Requested hypervisor hostname to create servers. Admin ' help=_(
'only by default. (supported by --os-compute-api-version ' 'Requested hypervisor hostname to create servers. '
'2.74 or above)'), '(admin only) '
'(supported by --os-compute-api-version 2.74 or above)'
),
) )
parser.add_argument( parser.add_argument(
'--boot-from-volume', '--boot-from-volume',
metavar='<volume-size>', metavar='<volume-size>',
type=int, type=int,
help=_('When used in conjunction with the ``--image`` or ' help=_(
'``--image-property`` option, this option automatically ' 'When used in conjunction with the ``--image`` or '
'creates a block device mapping with a boot index of 0 ' '``--image-property`` option, this option automatically '
'and tells the compute service to create a volume of the ' 'creates a block device mapping with a boot index of 0 '
'given size (in GB) from the specified image and use it ' 'and tells the compute service to create a volume of the '
'as the root disk of the server. The root volume will not ' 'given size (in GB) from the specified image and use it '
'be deleted when the server is deleted. This option is ' 'as the root disk of the server. The root volume will not '
'mutually exclusive with the ``--volume`` option.') 'be deleted when the server is deleted. This option is '
'mutually exclusive with the ``--volume`` option.'
)
) )
parser.add_argument( parser.add_argument(
'--block-device-mapping', '--block-device-mapping',
@ -738,37 +754,40 @@ class CreateServer(command.ShowOne):
# NOTE(RuiChen): Add '\n' at the end of line to put each item in # NOTE(RuiChen): Add '\n' at the end of line to put each item in
# the separated line, avoid the help message looks # the separated line, avoid the help message looks
# messy, see _SmartHelpFormatter in cliff. # messy, see _SmartHelpFormatter in cliff.
help=_('Create a block device on the server.\n' help=_(
'Block device mapping in the format\n' 'Create a block device on the server.\n'
'<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n' 'Block device mapping in the format\n'
'<dev-name>: block device name, like: vdb, xvdc ' '<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n'
'(required)\n' '<dev-name>: block device name, like: vdb, xvdc '
'<id>: Name or ID of the volume, volume snapshot or image ' '(required)\n'
'(required)\n' '<id>: Name or ID of the volume, volume snapshot or image '
'<type>: volume, snapshot or image; default: volume ' '(required)\n'
'(optional)\n' '<type>: volume, snapshot or image; default: volume '
'<size(GB)>: volume size if create from image or snapshot ' '(optional)\n'
'(optional)\n' '<size(GB)>: volume size if create from image or snapshot '
'<delete-on-terminate>: true or false; default: false ' '(optional)\n'
'(optional)\n' '<delete-on-terminate>: true or false; default: false '
'(optional extension)'), '(optional)\n'
),
) )
parser.add_argument( parser.add_argument(
'--nic', '--nic',
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
"port-id=port-uuid,auto,none>", "port-id=port-uuid,auto,none>",
action='append', action='append',
help=_("Create a NIC on the server. " help=_(
"Specify option multiple times to create multiple NICs. " "Create a NIC on the server. "
"Either net-id or port-id must be provided, but not both. " "Specify option multiple times to create multiple NICs. "
"net-id: attach NIC to network with this UUID, " "Either net-id or port-id must be provided, but not both. "
"port-id: attach NIC to port with this UUID, " "net-id: attach NIC to network with this UUID, "
"v4-fixed-ip: IPv4 fixed address for NIC (optional), " "port-id: attach NIC to port with this UUID, "
"v6-fixed-ip: IPv6 fixed address for NIC (optional), " "v4-fixed-ip: IPv4 fixed address for NIC (optional), "
"none: (v2.37+) no network is attached, " "v6-fixed-ip: IPv6 fixed address for NIC (optional), "
"auto: (v2.37+) the compute service will automatically " "none: (v2.37+) no network is attached, "
"allocate a network. Specifying a --nic of auto or none " "auto: (v2.37+) the compute service will automatically "
"cannot be used with any other --nic value."), "allocate a network. Specifying a --nic of auto or none "
"cannot be used with any other --nic value."
),
) )
parser.add_argument( parser.add_argument(
'--network', '--network',
@ -776,13 +795,15 @@ class CreateServer(command.ShowOne):
action='append', action='append',
dest='nic', dest='nic',
type=_prefix_checked_value('net-id='), type=_prefix_checked_value('net-id='),
help=_("Create a NIC on the server and connect it to network. " help=_(
"Specify option multiple times to create multiple NICs. " "Create a NIC on the server and connect it to network. "
"This is a wrapper for the '--nic net-id=<network>' " "Specify option multiple times to create multiple NICs. "
"parameter that provides simple syntax for the standard " "This is a wrapper for the '--nic net-id=<network>' "
"use case of connecting a new server to a given network. " "parameter that provides simple syntax for the standard "
"For more advanced use cases, refer to the '--nic' " "use case of connecting a new server to a given network. "
"parameter."), "For more advanced use cases, refer to the '--nic' "
"parameter."
),
) )
parser.add_argument( parser.add_argument(
'--port', '--port',
@ -790,12 +811,14 @@ class CreateServer(command.ShowOne):
action='append', action='append',
dest='nic', dest='nic',
type=_prefix_checked_value('port-id='), type=_prefix_checked_value('port-id='),
help=_("Create a NIC on the server and connect it to port. " help=_(
"Specify option multiple times to create multiple NICs. " "Create a NIC on the server and connect it to port. "
"This is a wrapper for the '--nic port-id=<port>' " "Specify option multiple times to create multiple NICs. "
"parameter that provides simple syntax for the standard " "This is a wrapper for the '--nic port-id=<port>' "
"use case of connecting a new server to a given port. For " "parameter that provides simple syntax for the standard "
"more advanced use cases, refer to the '--nic' parameter."), "use case of connecting a new server to a given port. For "
"more advanced use cases, refer to the '--nic' parameter."
),
) )
parser.add_argument( parser.add_argument(
'--hint', '--hint',
@ -882,10 +905,13 @@ class CreateServer(command.ShowOne):
if not image and parsed_args.image_property: if not image and parsed_args.image_property:
def emit_duplicated_warning(img, image_property): def emit_duplicated_warning(img, image_property):
img_uuid_list = [str(image.id) for image in img] img_uuid_list = [str(image.id) for image in img]
LOG.warning(_('Multiple matching images: %(img_uuid_list)s\n' LOG.warning(
'Using image: %(chosen_one)s') % 'Multiple matching images: %(img_uuid_list)s\n'
{'img_uuid_list': img_uuid_list, 'Using image: %(chosen_one)s',
'chosen_one': img_uuid_list[0]}) {
'img_uuid_list': img_uuid_list,
'chosen_one': img_uuid_list[0],
})
def _match_image(image_api, wanted_properties): def _match_image(image_api, wanted_properties):
image_list = image_api.images() image_list = image_api.images()
@ -902,45 +928,52 @@ class CreateServer(command.ShowOne):
set([key, value]) set([key, value])
except TypeError: except TypeError:
if key != 'properties': if key != 'properties':
LOG.debug('Skipped the \'%s\' attribute. ' LOG.debug(
'That cannot be compared. ' 'Skipped the \'%s\' attribute. '
'(image: %s, value: %s)', 'That cannot be compared. '
key, img.id, value) '(image: %s, value: %s)',
key, img.id, value,
)
pass pass
else: else:
img_dict[key] = value img_dict[key] = value
if all(k in img_dict and img_dict[k] == v if all(
for k, v in wanted_properties.items()): k in img_dict and img_dict[k] == v
for k, v in wanted_properties.items()
):
images_matched.append(img) images_matched.append(img)
return images_matched return images_matched
images = _match_image(image_client, parsed_args.image_property) images = _match_image(image_client, parsed_args.image_property)
if len(images) > 1: if len(images) > 1:
emit_duplicated_warning(images, emit_duplicated_warning(images, parsed_args.image_property)
parsed_args.image_property)
if images: if images:
image = images[0] image = images[0]
else: else:
raise exceptions.CommandError(_("No images match the " msg = _(
"property expected by " 'No images match the property expected by '
"--image-property")) '--image-property'
)
raise exceptions.CommandError(msg)
# Lookup parsed_args.volume # Lookup parsed_args.volume
volume = None volume = None
if parsed_args.volume: if parsed_args.volume:
# --volume and --boot-from-volume are mutually exclusive. # --volume and --boot-from-volume are mutually exclusive.
if parsed_args.boot_from_volume: if parsed_args.boot_from_volume:
raise exceptions.CommandError( msg = _('--volume is not allowed with --boot-from-volume')
_('--volume is not allowed with --boot-from-volume')) raise exceptions.CommandError(msg)
volume = utils.find_resource( volume = utils.find_resource(
volume_client.volumes, volume_client.volumes,
parsed_args.volume, parsed_args.volume,
).id ).id
# Lookup parsed_args.flavor # Lookup parsed_args.flavor
flavor = utils.find_resource(compute_client.flavors, flavor = utils.find_resource(
parsed_args.flavor) compute_client.flavors, parsed_args.flavor)
files = {} files = {}
for f in parsed_args.file: for f in parsed_args.file:
@ -950,16 +983,17 @@ class CreateServer(command.ShowOne):
except IOError as e: except IOError as e:
msg = _("Can't open '%(source)s': %(exception)s") msg = _("Can't open '%(source)s': %(exception)s")
raise exceptions.CommandError( raise exceptions.CommandError(
msg % {"source": src, msg % {'source': src, 'exception': e}
"exception": e}
) )
if parsed_args.min > parsed_args.max: if parsed_args.min > parsed_args.max:
msg = _("min instances should be <= max instances") msg = _("min instances should be <= max instances")
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
if parsed_args.min < 1: if parsed_args.min < 1:
msg = _("min instances should be > 0") msg = _("min instances should be > 0")
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
if parsed_args.max < 1: if parsed_args.max < 1:
msg = _("max instances should be > 0") msg = _("max instances should be > 0")
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
@ -971,8 +1005,7 @@ class CreateServer(command.ShowOne):
except IOError as e: except IOError as e:
msg = _("Can't open '%(data)s': %(exception)s") msg = _("Can't open '%(data)s': %(exception)s")
raise exceptions.CommandError( raise exceptions.CommandError(
msg % {"data": parsed_args.user_data, msg % {'data': parsed_args.user_data, 'exception': e}
"exception": e}
) )
if parsed_args.description: if parsed_args.description:
@ -983,11 +1016,12 @@ class CreateServer(command.ShowOne):
block_device_mapping_v2 = [] block_device_mapping_v2 = []
if volume: if volume:
block_device_mapping_v2 = [{'uuid': volume, block_device_mapping_v2 = [{
'boot_index': '0', 'uuid': volume,
'source_type': 'volume', 'boot_index': '0',
'destination_type': 'volume' 'source_type': 'volume',
}] 'destination_type': 'volume'
}]
elif parsed_args.boot_from_volume: elif parsed_args.boot_from_volume:
# Tell nova to create a root volume from the image provided. # Tell nova to create a root volume from the image provided.
block_device_mapping_v2 = [{ block_device_mapping_v2 = [{
@ -1008,13 +1042,16 @@ class CreateServer(command.ShowOne):
dev_map = dev_map.split(':') dev_map = dev_map.split(':')
if dev_map[0]: if dev_map[0]:
mapping = {'device_name': dev_name} mapping = {'device_name': dev_name}
# 1. decide source and destination type # 1. decide source and destination type
if (len(dev_map) > 1 and if (len(dev_map) > 1 and
dev_map[1] in ('volume', 'snapshot', 'image')): dev_map[1] in ('volume', 'snapshot', 'image')):
mapping['source_type'] = dev_map[1] mapping['source_type'] = dev_map[1]
else: else:
mapping['source_type'] = 'volume' mapping['source_type'] = 'volume'
mapping['destination_type'] = 'volume' mapping['destination_type'] = 'volume'
# 2. check target exist, update target uuid according by # 2. check target exist, update target uuid according by
# source type # source type
if mapping['source_type'] == 'volume': if mapping['source_type'] == 'volume':
@ -1040,14 +1077,18 @@ class CreateServer(command.ShowOne):
image_id = image_client.find_image(dev_map[0], image_id = image_client.find_image(dev_map[0],
ignore_missing=False).id ignore_missing=False).id
mapping['uuid'] = image_id mapping['uuid'] = image_id
# 3. append size and delete_on_termination if exist # 3. append size and delete_on_termination if exist
if len(dev_map) > 2 and dev_map[2]: if len(dev_map) > 2 and dev_map[2]:
mapping['volume_size'] = dev_map[2] mapping['volume_size'] = dev_map[2]
if len(dev_map) > 3 and dev_map[3]: if len(dev_map) > 3 and dev_map[3]:
mapping['delete_on_termination'] = dev_map[3] mapping['delete_on_termination'] = dev_map[3]
else: else:
msg = _("Volume, volume snapshot or image (name or ID) must " msg = _(
"be specified if --block-device-mapping is specified") 'Volume, volume snapshot or image (name or ID) must '
'be specified if --block-device-mapping is specified'
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
block_device_mapping_v2.append(mapping) block_device_mapping_v2.append(mapping)
@ -1061,22 +1102,32 @@ class CreateServer(command.ShowOne):
auto_or_none = True auto_or_none = True
nics.append(nic_str) nics.append(nic_str)
else: else:
nic_info = {"net-id": "", "v4-fixed-ip": "", nic_info = {
"v6-fixed-ip": "", "port-id": ""} 'net-id': '',
'v4-fixed-ip': '',
'v6-fixed-ip': '',
'port-id': '',
}
for kv_str in nic_str.split(","): for kv_str in nic_str.split(","):
k, sep, v = kv_str.partition("=") k, sep, v = kv_str.partition("=")
if k in nic_info and v: if k in nic_info and v:
nic_info[k] = v nic_info[k] = v
else: else:
msg = (_("Invalid nic argument '%s'. Nic arguments " msg = _(
"must be of the form --nic <net-id=net-uuid" "Invalid nic argument '%s'. Nic arguments "
",v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," "must be of the form --nic <net-id=net-uuid"
"port-id=port-uuid>.")) ",v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
"port-id=port-uuid>."
)
raise exceptions.CommandError(msg % k) raise exceptions.CommandError(msg % k)
if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): if bool(nic_info["net-id"]) == bool(nic_info["port-id"]):
msg = _("either network or port should be specified " msg = _(
"but not both") 'Either network or port should be specified '
'but not both'
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
if self.app.client_manager.is_network_endpoint_enabled(): if self.app.client_manager.is_network_endpoint_enabled():
network_client = self.app.client_manager.network network_client = self.app.client_manager.network
if nic_info["net-id"]: if nic_info["net-id"]:
@ -1093,17 +1144,22 @@ class CreateServer(command.ShowOne):
nic_info["net-id"] nic_info["net-id"]
)['id'] )['id']
if nic_info["port-id"]: if nic_info["port-id"]:
msg = _("can't create server with port specified " msg = _(
"since network endpoint not enabled") "Can't create server with port specified "
"since network endpoint not enabled"
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
nics.append(nic_info) nics.append(nic_info)
if nics: if nics:
if auto_or_none: if auto_or_none:
if len(nics) > 1: if len(nics) > 1:
msg = _('Specifying a --nic of auto or none cannot ' msg = _(
'be used with any other --nic, --network ' 'Specifying a --nic of auto or none cannot '
'or --port value.') 'be used with any other --nic, --network '
'or --port value.'
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
nics = nics[0] nics = nics[0]
else: else:
@ -1185,16 +1241,22 @@ class CreateServer(command.ShowOne):
if parsed_args.host: if parsed_args.host:
if compute_client.api_version < api_versions.APIVersion("2.74"): if compute_client.api_version < api_versions.APIVersion("2.74"):
msg = _("Specifying --host is not supported for " msg = _(
"--os-compute-api-version less than 2.74") '--os-compute-api-version 2.74 or greater is required to '
'support the --host option'
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
boot_kwargs['host'] = parsed_args.host boot_kwargs['host'] = parsed_args.host
if parsed_args.hypervisor_hostname: if parsed_args.hypervisor_hostname:
if compute_client.api_version < api_versions.APIVersion("2.74"): if compute_client.api_version < api_versions.APIVersion("2.74"):
msg = _("Specifying --hypervisor-hostname is not supported " msg = _(
"for --os-compute-api-version less than 2.74") '--os-compute-api-version 2.74 or greater is required to '
'support the --hypervisor-hostname option'
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
boot_kwargs['hypervisor_hostname'] = ( boot_kwargs['hypervisor_hostname'] = (
parsed_args.hypervisor_hostname) parsed_args.hypervisor_hostname)
@ -1220,8 +1282,7 @@ class CreateServer(command.ShowOne):
): ):
self.app.stdout.write('\n') self.app.stdout.write('\n')
else: else:
LOG.error(_('Error creating server: %s'), LOG.error('Error creating server: %s', parsed_args.server_name)
parsed_args.server_name)
self.app.stdout.write(_('Error creating server\n')) self.app.stdout.write(_('Error creating server\n'))
raise SystemExit raise SystemExit