Add admin_password support
Mogan should support to inject an admin password into a bare metal server. DocImpact APIImpact Change-Id: Id3487b7aea699353aedd49a51d9a5a2e250943b9 Implements: bp admin-password-support
This commit is contained in:
parent
3b1dc23cf3
commit
eeabfedc54
@ -155,6 +155,13 @@ addresses:
|
||||
in: body
|
||||
required: true
|
||||
type: object
|
||||
adminPass:
|
||||
description: |
|
||||
The administrative password of the server. If you omit this parameter, the operation
|
||||
generates a new password.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
affinity_zone:
|
||||
description: |
|
||||
The affinity zone which the server belongs to.
|
||||
|
@ -28,7 +28,8 @@
|
||||
}
|
||||
],
|
||||
"user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"key_name": "test_key"
|
||||
"key_name": "test_key",
|
||||
"adminPass": "Qv7Lsc35H4xM"
|
||||
},
|
||||
"scheduler_hints": {
|
||||
"group": "group1"
|
||||
|
@ -42,6 +42,7 @@ Request
|
||||
- metadata: metadata
|
||||
- user_data: user_data
|
||||
- personality: personality
|
||||
- adminPass: adminPass
|
||||
- key_name: key_name
|
||||
- partitions: partitions
|
||||
- scheduler_hints: scheduler_hints
|
||||
|
@ -27,6 +27,7 @@ create_server = {
|
||||
'availability_zone': parameter_types.availability_zone,
|
||||
'image_uuid': parameter_types.image_id,
|
||||
'flavor_uuid': parameter_types.flavor_id,
|
||||
'adminPass': parameter_types.admin_password,
|
||||
'networks': {
|
||||
'type': 'array', 'minItems': 1,
|
||||
'items': {
|
||||
|
@ -698,6 +698,8 @@ class ServerController(ServerControllerBase):
|
||||
partitions = server.get('partitions')
|
||||
personality = server.pop('personality', None)
|
||||
|
||||
password = self._get_server_admin_password(server)
|
||||
|
||||
injected_files = []
|
||||
if personality:
|
||||
for item in personality:
|
||||
@ -717,6 +719,7 @@ class ServerController(ServerControllerBase):
|
||||
requested_networks=requested_networks,
|
||||
user_data=user_data,
|
||||
injected_files=injected_files,
|
||||
admin_password=password,
|
||||
key_name=key_name,
|
||||
min_count=min_count,
|
||||
max_count=max_count,
|
||||
@ -726,6 +729,14 @@ class ServerController(ServerControllerBase):
|
||||
pecan.response.location = link.build_url('server', servers[0].uuid)
|
||||
return Server.convert_with_links(servers[0])
|
||||
|
||||
def _get_server_admin_password(self, server):
|
||||
"""Determine the admin password for a server on creation."""
|
||||
if 'adminPass' in server:
|
||||
password = server['adminPass']
|
||||
else:
|
||||
password = api_utils.generate_password()
|
||||
return password
|
||||
|
||||
@policy.authorize_wsgi("mogan:server", "update")
|
||||
@wsme.validate(types.uuid, [ServerPatchType])
|
||||
@expose.expose(Server, types.uuid, body=[ServerPatchType])
|
||||
|
@ -15,8 +15,10 @@
|
||||
|
||||
import jsonpatch
|
||||
from oslo_config import cfg
|
||||
import random
|
||||
import wsme
|
||||
|
||||
|
||||
from mogan.common.i18n import _
|
||||
|
||||
|
||||
@ -27,6 +29,12 @@ JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
|
||||
jsonpatch.JsonPointerException,
|
||||
KeyError)
|
||||
|
||||
# Default symbols to use for passwords. Avoids visually confusing characters.
|
||||
# ~6 bits per symbol
|
||||
DEFAULT_PASSWORD_SYMBOLS = ('23456789', # Removed: 0,1
|
||||
'ABCDEFGHJKLMNPQRSTUVWXYZ', # Removed: I, O
|
||||
'abcdefghijkmnopqrstuvwxyz') # Removed: l
|
||||
|
||||
|
||||
def validate_limit(limit):
|
||||
if limit is None:
|
||||
@ -82,3 +90,35 @@ def show_nics(nics):
|
||||
ret_nics.append({key: value for key, value in nic.items() if key in
|
||||
show_keys})
|
||||
return ret_nics
|
||||
|
||||
|
||||
def generate_password(length=None, symbolgroups=DEFAULT_PASSWORD_SYMBOLS):
|
||||
"""Generate a random password from the supplied symbol groups.
|
||||
|
||||
At least one symbol from each group will be included. Unpredictable
|
||||
results if length is less than the number of symbol groups.
|
||||
|
||||
Believed to be reasonably secure (with a reasonable password length!)
|
||||
|
||||
"""
|
||||
if length is None:
|
||||
length = CONF.password_length
|
||||
|
||||
r = random.SystemRandom()
|
||||
|
||||
password = [r.choice(s) for s in symbolgroups]
|
||||
# If length < len(symbolgroups), the leading characters will only
|
||||
# be from the first length groups. Try our best to not be predictable
|
||||
# by shuffling and then truncating.
|
||||
r.shuffle(password)
|
||||
password = password[:length]
|
||||
length -= len(password)
|
||||
|
||||
# then fill with random characters from all symbol groups
|
||||
symbols = ''.join(symbolgroups)
|
||||
password.extend([r.choice(symbols) for _i in range(length)])
|
||||
|
||||
# finally shuffle to ensure first x characters aren't from a
|
||||
# predictable group
|
||||
r.shuffle(password)
|
||||
return ''.join(password)
|
||||
|
@ -59,6 +59,14 @@ network_port_id = {
|
||||
'type': 'string', 'format': 'uuid'
|
||||
}
|
||||
|
||||
admin_password = {
|
||||
# NOTE: admin_password is the admin password of a server
|
||||
# instance, and it is not stored into mogan's data base.
|
||||
# In addition, users set sometimes long/strange string
|
||||
# as password. It is unnecessary to limit string length
|
||||
# and string pattern.
|
||||
'type': 'string',
|
||||
}
|
||||
|
||||
flavor_id = {
|
||||
'type': 'string', 'format': 'uuid'
|
||||
|
@ -27,6 +27,10 @@ api_opts = [
|
||||
help=_('Return server tracebacks in the API response for any '
|
||||
'error responses. WARNING: this is insecure '
|
||||
'and should not be used in a production environment.')),
|
||||
cfg.IntOpt('password_length',
|
||||
default=12,
|
||||
min=0,
|
||||
help='Length of generated server admin passwords.'),
|
||||
]
|
||||
|
||||
exc_log_opts = [
|
||||
|
@ -315,8 +315,8 @@ class API(object):
|
||||
def _create_server(self, context, flavor, image_uuid,
|
||||
name, description, availability_zone, metadata,
|
||||
requested_networks, user_data, injected_files,
|
||||
key_name, min_count, max_count, partitions,
|
||||
scheduler_hints):
|
||||
admin_password, key_name, min_count, max_count,
|
||||
partitions, scheduler_hints):
|
||||
"""Verify all the input parameters"""
|
||||
image = self._get_image(context, image_uuid)
|
||||
iwdi = self._is_whole_disk_image(context, image)
|
||||
@ -369,6 +369,7 @@ class API(object):
|
||||
requested_networks,
|
||||
user_data,
|
||||
decoded_files,
|
||||
admin_password,
|
||||
key_pair,
|
||||
partitions,
|
||||
request_spec,
|
||||
@ -378,8 +379,9 @@ class API(object):
|
||||
def create(self, context, flavor, image_uuid,
|
||||
name=None, description=None, availability_zone=None,
|
||||
metadata=None, requested_networks=None, user_data=None,
|
||||
injected_files=None, key_name=None, min_count=None,
|
||||
max_count=None, partitions=None, scheduler_hints=None):
|
||||
injected_files=None, admin_password=None, key_name=None,
|
||||
min_count=None, max_count=None, partitions=None,
|
||||
scheduler_hints=None):
|
||||
"""Provision servers
|
||||
|
||||
Sending server information to the engine and will handle
|
||||
@ -396,7 +398,7 @@ class API(object):
|
||||
image_uuid, name, description,
|
||||
availability_zone, metadata,
|
||||
requested_networks, user_data,
|
||||
injected_files, key_name,
|
||||
injected_files, admin_password, key_name,
|
||||
min_count, max_count, partitions,
|
||||
scheduler_hints)
|
||||
|
||||
|
@ -49,7 +49,7 @@ class OnFailureRescheduleTask(flow_utils.MoganTask):
|
||||
def __init__(self, engine_rpcapi):
|
||||
requires = ['filter_properties', 'request_spec', 'server',
|
||||
'requested_networks', 'user_data', 'injected_files',
|
||||
'key_pair', 'partitions', 'context']
|
||||
'admin_password', 'key_pair', 'partitions', 'context']
|
||||
super(OnFailureRescheduleTask, self).__init__(addons=[ACTION],
|
||||
requires=requires)
|
||||
self.engine_rpcapi = engine_rpcapi
|
||||
@ -68,7 +68,7 @@ class OnFailureRescheduleTask(flow_utils.MoganTask):
|
||||
|
||||
def _reschedule(self, context, cause, request_spec, filter_properties,
|
||||
server, requested_networks, user_data, injected_files,
|
||||
key_pair, partitions):
|
||||
admin_password, key_pair, partitions):
|
||||
"""Actions that happen during the rescheduling attempt occur here."""
|
||||
|
||||
create_server = self.engine_rpcapi.schedule_and_create_servers
|
||||
@ -95,6 +95,7 @@ class OnFailureRescheduleTask(flow_utils.MoganTask):
|
||||
return create_server(context, [server], requested_networks,
|
||||
user_data=user_data,
|
||||
injected_files=injected_files,
|
||||
admin_password=admin_password,
|
||||
key_pair=key_pair,
|
||||
partitions=partitions,
|
||||
request_spec=request_spec,
|
||||
@ -218,17 +219,22 @@ class GenerateConfigDriveTask(flow_utils.MoganTask):
|
||||
"""Generate ConfigDrive value the server."""
|
||||
|
||||
def __init__(self):
|
||||
requires = ['server', 'user_data', 'injected_files', 'key_pair',
|
||||
'configdrive', 'context']
|
||||
requires = ['server', 'user_data', 'injected_files', 'admin_password',
|
||||
'key_pair', 'configdrive', 'context']
|
||||
super(GenerateConfigDriveTask, self).__init__(addons=[ACTION],
|
||||
requires=requires)
|
||||
|
||||
def _generate_configdrive(self, context, server, user_data=None,
|
||||
files=None, key_pair=None):
|
||||
files=None, admin_password=None, key_pair=None):
|
||||
"""Generate a config drive."""
|
||||
extra_md = {}
|
||||
if admin_password:
|
||||
extra_md['admin_pass'] = admin_password
|
||||
|
||||
i_meta = server_metadata.ServerMetadata(
|
||||
server, content=files, user_data=user_data, key_pair=key_pair)
|
||||
server, content=files, user_data=user_data, key_pair=key_pair,
|
||||
extra_md=extra_md)
|
||||
|
||||
with tempfile.NamedTemporaryFile() as uncompressed:
|
||||
with configdrive.ConfigDriveBuilder(server_md=i_meta) as cdb:
|
||||
cdb.make_drive(uncompressed.name)
|
||||
@ -243,13 +249,12 @@ class GenerateConfigDriveTask(flow_utils.MoganTask):
|
||||
compressed.seek(0)
|
||||
return base64.b64encode(compressed.read())
|
||||
|
||||
def execute(self, context, server, user_data, injected_files, key_pair,
|
||||
configdrive):
|
||||
|
||||
def execute(self, context, server, user_data, injected_files,
|
||||
admin_password, key_pair, configdrive):
|
||||
try:
|
||||
configdrive['value'] = self._generate_configdrive(
|
||||
context, server, user_data=user_data, files=injected_files,
|
||||
key_pair=key_pair)
|
||||
admin_password=admin_password, key_pair=key_pair)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
msg = ("Failed to build configdrive: %s" %
|
||||
@ -282,8 +287,8 @@ class CreateServerTask(flow_utils.MoganTask):
|
||||
|
||||
|
||||
def get_flow(context, manager, server, requested_networks, user_data,
|
||||
injected_files, key_pair, partitions, request_spec,
|
||||
filter_properties):
|
||||
injected_files, admin_password, key_pair, partitions,
|
||||
request_spec, filter_properties):
|
||||
|
||||
"""Constructs and returns the manager entrypoint flow
|
||||
|
||||
@ -309,6 +314,7 @@ def get_flow(context, manager, server, requested_networks, user_data,
|
||||
'requested_networks': requested_networks,
|
||||
'user_data': user_data,
|
||||
'injected_files': injected_files,
|
||||
'admin_password': admin_password,
|
||||
'key_pair': key_pair,
|
||||
'partitions': partitions,
|
||||
'configdrive': {}
|
||||
|
@ -303,6 +303,7 @@ class EngineManager(base_manager.BaseEngineManager):
|
||||
requested_networks,
|
||||
user_data,
|
||||
injected_files,
|
||||
admin_password,
|
||||
key_pair,
|
||||
partitions,
|
||||
request_spec=None,
|
||||
@ -357,6 +358,7 @@ class EngineManager(base_manager.BaseEngineManager):
|
||||
requested_networks,
|
||||
user_data,
|
||||
injected_files,
|
||||
admin_password,
|
||||
key_pair,
|
||||
partitions,
|
||||
request_spec,
|
||||
@ -364,8 +366,8 @@ class EngineManager(base_manager.BaseEngineManager):
|
||||
|
||||
@wrap_server_fault
|
||||
def _create_server(self, context, server, requested_networks,
|
||||
user_data, injected_files, key_pair, partitions,
|
||||
request_spec=None, filter_properties=None):
|
||||
user_data, injected_files, admin_password, key_pair,
|
||||
partitions, request_spec=None, filter_properties=None):
|
||||
"""Perform a deployment."""
|
||||
LOG.debug("Creating server: %s", server)
|
||||
notifications.notify_about_server_action(
|
||||
@ -384,6 +386,7 @@ class EngineManager(base_manager.BaseEngineManager):
|
||||
requested_networks,
|
||||
user_data,
|
||||
injected_files,
|
||||
admin_password,
|
||||
key_pair,
|
||||
partitions,
|
||||
request_spec,
|
||||
|
@ -54,8 +54,8 @@ class InvalidMetadataPath(Exception):
|
||||
class ServerMetadata(object):
|
||||
"""Server metadata."""
|
||||
|
||||
def __init__(self, server, content=None, user_data=None,
|
||||
key_pair=None, extra_md=None):
|
||||
def __init__(self, server, content=None, user_data=None, key_pair=None,
|
||||
extra_md=None):
|
||||
"""Creation of this object should basically cover all time consuming
|
||||
collection. Methods after that should not cause time delays due to
|
||||
network operations or lengthy cpu operations.
|
||||
@ -66,8 +66,8 @@ class ServerMetadata(object):
|
||||
if not content:
|
||||
content = []
|
||||
|
||||
self.server = server
|
||||
self.extra_md = extra_md
|
||||
self.server = server
|
||||
self.availability_zone = server.availability_zone
|
||||
|
||||
if user_data is not None:
|
||||
|
@ -50,8 +50,8 @@ class EngineAPI(object):
|
||||
serializer=serializer)
|
||||
|
||||
def schedule_and_create_servers(self, context, servers, requested_networks,
|
||||
user_data, injected_files, key_pair,
|
||||
partitions, request_spec,
|
||||
user_data, injected_files, admin_password,
|
||||
key_pair, partitions, request_spec,
|
||||
filter_properties):
|
||||
"""Signal to engine service to perform a deployment."""
|
||||
cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
|
||||
@ -59,6 +59,7 @@ class EngineAPI(object):
|
||||
requested_networks=requested_networks,
|
||||
user_data=user_data,
|
||||
injected_files=injected_files,
|
||||
admin_password=admin_password,
|
||||
key_pair=key_pair,
|
||||
partitions=partitions,
|
||||
request_spec=request_spec,
|
||||
|
@ -224,6 +224,7 @@ class ComputeAPIUnitTest(base.DbTestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
min_count,
|
||||
max_count)
|
||||
|
||||
|
@ -109,6 +109,7 @@ class RPCAPITestCase(base.DbTestCase):
|
||||
requested_networks=[],
|
||||
user_data=None,
|
||||
injected_files=None,
|
||||
admin_password=None,
|
||||
key_pair=None,
|
||||
partitions=None,
|
||||
request_spec=None,
|
||||
|
Loading…
Reference in New Issue
Block a user