Merge "Add admin_password support"
This commit is contained in:
commit
2cee9e9107
@ -191,6 +191,13 @@ addresses:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: object
|
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:
|
affinity_zone:
|
||||||
description: |
|
description: |
|
||||||
The affinity zone which the server belongs to.
|
The affinity zone which the server belongs to.
|
||||||
|
@ -28,7 +28,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
"user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||||
"key_name": "test_key"
|
"key_name": "test_key",
|
||||||
|
"adminPass": "Qv7Lsc35H4xM"
|
||||||
},
|
},
|
||||||
"scheduler_hints": {
|
"scheduler_hints": {
|
||||||
"group": "group1"
|
"group": "group1"
|
||||||
|
@ -42,6 +42,7 @@ Request
|
|||||||
- metadata: metadata
|
- metadata: metadata
|
||||||
- user_data: user_data
|
- user_data: user_data
|
||||||
- personality: personality
|
- personality: personality
|
||||||
|
- adminPass: adminPass
|
||||||
- key_name: key_name
|
- key_name: key_name
|
||||||
- partitions: partitions
|
- partitions: partitions
|
||||||
- scheduler_hints: scheduler_hints
|
- scheduler_hints: scheduler_hints
|
||||||
|
@ -27,6 +27,7 @@ create_server = {
|
|||||||
'availability_zone': parameter_types.availability_zone,
|
'availability_zone': parameter_types.availability_zone,
|
||||||
'image_uuid': parameter_types.image_id,
|
'image_uuid': parameter_types.image_id,
|
||||||
'flavor_uuid': parameter_types.flavor_id,
|
'flavor_uuid': parameter_types.flavor_id,
|
||||||
|
'adminPass': parameter_types.admin_password,
|
||||||
'networks': {
|
'networks': {
|
||||||
'type': 'array', 'minItems': 1,
|
'type': 'array', 'minItems': 1,
|
||||||
'items': {
|
'items': {
|
||||||
|
@ -756,6 +756,8 @@ class ServerController(ServerControllerBase):
|
|||||||
partitions = server.get('partitions')
|
partitions = server.get('partitions')
|
||||||
personality = server.pop('personality', None)
|
personality = server.pop('personality', None)
|
||||||
|
|
||||||
|
password = self._get_server_admin_password(server)
|
||||||
|
|
||||||
injected_files = []
|
injected_files = []
|
||||||
if personality:
|
if personality:
|
||||||
for item in personality:
|
for item in personality:
|
||||||
@ -775,6 +777,7 @@ class ServerController(ServerControllerBase):
|
|||||||
requested_networks=requested_networks,
|
requested_networks=requested_networks,
|
||||||
user_data=user_data,
|
user_data=user_data,
|
||||||
injected_files=injected_files,
|
injected_files=injected_files,
|
||||||
|
admin_password=password,
|
||||||
key_name=key_name,
|
key_name=key_name,
|
||||||
min_count=min_count,
|
min_count=min_count,
|
||||||
max_count=max_count,
|
max_count=max_count,
|
||||||
@ -784,6 +787,14 @@ class ServerController(ServerControllerBase):
|
|||||||
pecan.response.location = link.build_url('server', servers[0].uuid)
|
pecan.response.location = link.build_url('server', servers[0].uuid)
|
||||||
return Server.convert_with_links(servers[0])
|
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")
|
@policy.authorize_wsgi("mogan:server", "update")
|
||||||
@wsme.validate(types.uuid, [ServerPatchType])
|
@wsme.validate(types.uuid, [ServerPatchType])
|
||||||
@expose.expose(Server, types.uuid, body=[ServerPatchType])
|
@expose.expose(Server, types.uuid, body=[ServerPatchType])
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
|
|
||||||
import jsonpatch
|
import jsonpatch
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
import random
|
||||||
import wsme
|
import wsme
|
||||||
|
|
||||||
|
|
||||||
from mogan.common.i18n import _
|
from mogan.common.i18n import _
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +29,12 @@ JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
|
|||||||
jsonpatch.JsonPointerException,
|
jsonpatch.JsonPointerException,
|
||||||
KeyError)
|
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):
|
def validate_limit(limit):
|
||||||
if limit is None:
|
if limit is None:
|
||||||
@ -81,3 +89,35 @@ def show_nics(nics):
|
|||||||
ret_nics.append({key: value for key, value in nic.items() if key in
|
ret_nics.append({key: value for key, value in nic.items() if key in
|
||||||
show_keys})
|
show_keys})
|
||||||
return ret_nics
|
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'
|
'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 = {
|
flavor_id = {
|
||||||
'type': 'string', 'format': 'uuid'
|
'type': 'string', 'format': 'uuid'
|
||||||
|
@ -27,6 +27,10 @@ api_opts = [
|
|||||||
help=_('Return server tracebacks in the API response for any '
|
help=_('Return server tracebacks in the API response for any '
|
||||||
'error responses. WARNING: this is insecure '
|
'error responses. WARNING: this is insecure '
|
||||||
'and should not be used in a production environment.')),
|
'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 = [
|
exc_log_opts = [
|
||||||
|
@ -315,8 +315,8 @@ class API(object):
|
|||||||
def _create_server(self, context, flavor, image_uuid,
|
def _create_server(self, context, flavor, image_uuid,
|
||||||
name, description, availability_zone, metadata,
|
name, description, availability_zone, metadata,
|
||||||
requested_networks, user_data, injected_files,
|
requested_networks, user_data, injected_files,
|
||||||
key_name, min_count, max_count, partitions,
|
admin_password, key_name, min_count, max_count,
|
||||||
scheduler_hints):
|
partitions, scheduler_hints):
|
||||||
"""Verify all the input parameters"""
|
"""Verify all the input parameters"""
|
||||||
image = self._get_image(context, image_uuid)
|
image = self._get_image(context, image_uuid)
|
||||||
iwdi = self._is_whole_disk_image(context, image)
|
iwdi = self._is_whole_disk_image(context, image)
|
||||||
@ -369,6 +369,7 @@ class API(object):
|
|||||||
requested_networks,
|
requested_networks,
|
||||||
user_data,
|
user_data,
|
||||||
decoded_files,
|
decoded_files,
|
||||||
|
admin_password,
|
||||||
key_pair,
|
key_pair,
|
||||||
partitions,
|
partitions,
|
||||||
request_spec,
|
request_spec,
|
||||||
@ -378,8 +379,9 @@ class API(object):
|
|||||||
def create(self, context, flavor, image_uuid,
|
def create(self, context, flavor, image_uuid,
|
||||||
name=None, description=None, availability_zone=None,
|
name=None, description=None, availability_zone=None,
|
||||||
metadata=None, requested_networks=None, user_data=None,
|
metadata=None, requested_networks=None, user_data=None,
|
||||||
injected_files=None, key_name=None, min_count=None,
|
injected_files=None, admin_password=None, key_name=None,
|
||||||
max_count=None, partitions=None, scheduler_hints=None):
|
min_count=None, max_count=None, partitions=None,
|
||||||
|
scheduler_hints=None):
|
||||||
"""Provision servers
|
"""Provision servers
|
||||||
|
|
||||||
Sending server information to the engine and will handle
|
Sending server information to the engine and will handle
|
||||||
@ -396,7 +398,7 @@ class API(object):
|
|||||||
image_uuid, name, description,
|
image_uuid, name, description,
|
||||||
availability_zone, metadata,
|
availability_zone, metadata,
|
||||||
requested_networks, user_data,
|
requested_networks, user_data,
|
||||||
injected_files, key_name,
|
injected_files, admin_password, key_name,
|
||||||
min_count, max_count, partitions,
|
min_count, max_count, partitions,
|
||||||
scheduler_hints)
|
scheduler_hints)
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class OnFailureRescheduleTask(flow_utils.MoganTask):
|
|||||||
def __init__(self, engine_rpcapi):
|
def __init__(self, engine_rpcapi):
|
||||||
requires = ['filter_properties', 'request_spec', 'server',
|
requires = ['filter_properties', 'request_spec', 'server',
|
||||||
'requested_networks', 'user_data', 'injected_files',
|
'requested_networks', 'user_data', 'injected_files',
|
||||||
'key_pair', 'partitions', 'context']
|
'admin_password', 'key_pair', 'partitions', 'context']
|
||||||
super(OnFailureRescheduleTask, self).__init__(addons=[ACTION],
|
super(OnFailureRescheduleTask, self).__init__(addons=[ACTION],
|
||||||
requires=requires)
|
requires=requires)
|
||||||
self.engine_rpcapi = engine_rpcapi
|
self.engine_rpcapi = engine_rpcapi
|
||||||
@ -68,7 +68,7 @@ class OnFailureRescheduleTask(flow_utils.MoganTask):
|
|||||||
|
|
||||||
def _reschedule(self, context, cause, request_spec, filter_properties,
|
def _reschedule(self, context, cause, request_spec, filter_properties,
|
||||||
server, requested_networks, user_data, injected_files,
|
server, requested_networks, user_data, injected_files,
|
||||||
key_pair, partitions):
|
admin_password, key_pair, partitions):
|
||||||
"""Actions that happen during the rescheduling attempt occur here."""
|
"""Actions that happen during the rescheduling attempt occur here."""
|
||||||
|
|
||||||
create_server = self.engine_rpcapi.schedule_and_create_servers
|
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,
|
return create_server(context, [server], requested_networks,
|
||||||
user_data=user_data,
|
user_data=user_data,
|
||||||
injected_files=injected_files,
|
injected_files=injected_files,
|
||||||
|
admin_password=admin_password,
|
||||||
key_pair=key_pair,
|
key_pair=key_pair,
|
||||||
partitions=partitions,
|
partitions=partitions,
|
||||||
request_spec=request_spec,
|
request_spec=request_spec,
|
||||||
@ -218,17 +219,22 @@ class GenerateConfigDriveTask(flow_utils.MoganTask):
|
|||||||
"""Generate ConfigDrive value the server."""
|
"""Generate ConfigDrive value the server."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
requires = ['server', 'user_data', 'injected_files', 'key_pair',
|
requires = ['server', 'user_data', 'injected_files', 'admin_password',
|
||||||
'configdrive', 'context']
|
'key_pair', 'configdrive', 'context']
|
||||||
super(GenerateConfigDriveTask, self).__init__(addons=[ACTION],
|
super(GenerateConfigDriveTask, self).__init__(addons=[ACTION],
|
||||||
requires=requires)
|
requires=requires)
|
||||||
|
|
||||||
def _generate_configdrive(self, context, server, user_data=None,
|
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."""
|
"""Generate a config drive."""
|
||||||
|
extra_md = {}
|
||||||
|
if admin_password:
|
||||||
|
extra_md['admin_pass'] = admin_password
|
||||||
|
|
||||||
i_meta = server_metadata.ServerMetadata(
|
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 tempfile.NamedTemporaryFile() as uncompressed:
|
||||||
with configdrive.ConfigDriveBuilder(server_md=i_meta) as cdb:
|
with configdrive.ConfigDriveBuilder(server_md=i_meta) as cdb:
|
||||||
cdb.make_drive(uncompressed.name)
|
cdb.make_drive(uncompressed.name)
|
||||||
@ -243,13 +249,12 @@ class GenerateConfigDriveTask(flow_utils.MoganTask):
|
|||||||
compressed.seek(0)
|
compressed.seek(0)
|
||||||
return base64.b64encode(compressed.read())
|
return base64.b64encode(compressed.read())
|
||||||
|
|
||||||
def execute(self, context, server, user_data, injected_files, key_pair,
|
def execute(self, context, server, user_data, injected_files,
|
||||||
configdrive):
|
admin_password, key_pair, configdrive):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
configdrive['value'] = self._generate_configdrive(
|
configdrive['value'] = self._generate_configdrive(
|
||||||
context, server, user_data=user_data, files=injected_files,
|
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:
|
except Exception as e:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
msg = ("Failed to build configdrive: %s" %
|
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,
|
def get_flow(context, manager, server, requested_networks, user_data,
|
||||||
injected_files, key_pair, partitions, request_spec,
|
injected_files, admin_password, key_pair, partitions,
|
||||||
filter_properties):
|
request_spec, filter_properties):
|
||||||
|
|
||||||
"""Constructs and returns the manager entrypoint flow
|
"""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,
|
'requested_networks': requested_networks,
|
||||||
'user_data': user_data,
|
'user_data': user_data,
|
||||||
'injected_files': injected_files,
|
'injected_files': injected_files,
|
||||||
|
'admin_password': admin_password,
|
||||||
'key_pair': key_pair,
|
'key_pair': key_pair,
|
||||||
'partitions': partitions,
|
'partitions': partitions,
|
||||||
'configdrive': {}
|
'configdrive': {}
|
||||||
|
@ -303,6 +303,7 @@ class EngineManager(base_manager.BaseEngineManager):
|
|||||||
requested_networks,
|
requested_networks,
|
||||||
user_data,
|
user_data,
|
||||||
injected_files,
|
injected_files,
|
||||||
|
admin_password,
|
||||||
key_pair,
|
key_pair,
|
||||||
partitions,
|
partitions,
|
||||||
request_spec=None,
|
request_spec=None,
|
||||||
@ -357,6 +358,7 @@ class EngineManager(base_manager.BaseEngineManager):
|
|||||||
requested_networks,
|
requested_networks,
|
||||||
user_data,
|
user_data,
|
||||||
injected_files,
|
injected_files,
|
||||||
|
admin_password,
|
||||||
key_pair,
|
key_pair,
|
||||||
partitions,
|
partitions,
|
||||||
request_spec,
|
request_spec,
|
||||||
@ -364,8 +366,8 @@ class EngineManager(base_manager.BaseEngineManager):
|
|||||||
|
|
||||||
@wrap_server_fault
|
@wrap_server_fault
|
||||||
def _create_server(self, context, server, requested_networks,
|
def _create_server(self, context, server, requested_networks,
|
||||||
user_data, injected_files, key_pair, partitions,
|
user_data, injected_files, admin_password, key_pair,
|
||||||
request_spec=None, filter_properties=None):
|
partitions, request_spec=None, filter_properties=None):
|
||||||
"""Perform a deployment."""
|
"""Perform a deployment."""
|
||||||
LOG.debug("Creating server: %s", server)
|
LOG.debug("Creating server: %s", server)
|
||||||
notifications.notify_about_server_action(
|
notifications.notify_about_server_action(
|
||||||
@ -384,6 +386,7 @@ class EngineManager(base_manager.BaseEngineManager):
|
|||||||
requested_networks,
|
requested_networks,
|
||||||
user_data,
|
user_data,
|
||||||
injected_files,
|
injected_files,
|
||||||
|
admin_password,
|
||||||
key_pair,
|
key_pair,
|
||||||
partitions,
|
partitions,
|
||||||
request_spec,
|
request_spec,
|
||||||
|
@ -54,8 +54,8 @@ class InvalidMetadataPath(Exception):
|
|||||||
class ServerMetadata(object):
|
class ServerMetadata(object):
|
||||||
"""Server metadata."""
|
"""Server metadata."""
|
||||||
|
|
||||||
def __init__(self, server, content=None, user_data=None,
|
def __init__(self, server, content=None, user_data=None, key_pair=None,
|
||||||
key_pair=None, extra_md=None):
|
extra_md=None):
|
||||||
"""Creation of this object should basically cover all time consuming
|
"""Creation of this object should basically cover all time consuming
|
||||||
collection. Methods after that should not cause time delays due to
|
collection. Methods after that should not cause time delays due to
|
||||||
network operations or lengthy cpu operations.
|
network operations or lengthy cpu operations.
|
||||||
@ -66,8 +66,8 @@ class ServerMetadata(object):
|
|||||||
if not content:
|
if not content:
|
||||||
content = []
|
content = []
|
||||||
|
|
||||||
self.server = server
|
|
||||||
self.extra_md = extra_md
|
self.extra_md = extra_md
|
||||||
|
self.server = server
|
||||||
self.availability_zone = server.availability_zone
|
self.availability_zone = server.availability_zone
|
||||||
|
|
||||||
if user_data is not None:
|
if user_data is not None:
|
||||||
|
@ -50,8 +50,8 @@ class EngineAPI(object):
|
|||||||
serializer=serializer)
|
serializer=serializer)
|
||||||
|
|
||||||
def schedule_and_create_servers(self, context, servers, requested_networks,
|
def schedule_and_create_servers(self, context, servers, requested_networks,
|
||||||
user_data, injected_files, key_pair,
|
user_data, injected_files, admin_password,
|
||||||
partitions, request_spec,
|
key_pair, partitions, request_spec,
|
||||||
filter_properties):
|
filter_properties):
|
||||||
"""Signal to engine service to perform a deployment."""
|
"""Signal to engine service to perform a deployment."""
|
||||||
cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
|
cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
|
||||||
@ -59,6 +59,7 @@ class EngineAPI(object):
|
|||||||
requested_networks=requested_networks,
|
requested_networks=requested_networks,
|
||||||
user_data=user_data,
|
user_data=user_data,
|
||||||
injected_files=injected_files,
|
injected_files=injected_files,
|
||||||
|
admin_password=admin_password,
|
||||||
key_pair=key_pair,
|
key_pair=key_pair,
|
||||||
partitions=partitions,
|
partitions=partitions,
|
||||||
request_spec=request_spec,
|
request_spec=request_spec,
|
||||||
|
@ -224,6 +224,7 @@ class ComputeAPIUnitTest(base.DbTestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
min_count,
|
min_count,
|
||||||
max_count)
|
max_count)
|
||||||
|
|
||||||
|
@ -109,6 +109,7 @@ class RPCAPITestCase(base.DbTestCase):
|
|||||||
requested_networks=[],
|
requested_networks=[],
|
||||||
user_data=None,
|
user_data=None,
|
||||||
injected_files=None,
|
injected_files=None,
|
||||||
|
admin_password=None,
|
||||||
key_pair=None,
|
key_pair=None,
|
||||||
partitions=None,
|
partitions=None,
|
||||||
request_spec=None,
|
request_spec=None,
|
||||||
|
Loading…
Reference in New Issue
Block a user