Implement user ssh key injection for deployed nodes

Add authorized_keys field to Site object
Add sshkey to maasdriver models
Add ConfigureUserCredentials task
Add orchestrator step for ConfigureUserCredentials
Add driver logic to implement ConfigureUserCredentials
This commit is contained in:
Scott Hussey 2017-07-01 21:09:35 -05:00
parent 2f7b944903
commit a27d003b5d
9 changed files with 142 additions and 11 deletions

View File

@ -80,6 +80,7 @@ class DrydockConfig(object):
timeout_options = [
cfg.IntOpt('drydock_timeout', default=5, help='Fallback timeout when a specific one is not configured'),
cfg.IntOpt('create_network_template', default=2, help='Timeout in minutes for creating site network templates'),
cfg.IntOpt('configure_user_credentials', default=2, help='Timeout in minutes for creating user credentials'),
cfg.IntOpt('identify_node', default=10, help='Timeout in minutes for initial node identification'),
cfg.IntOpt('configure_hardware', default=30, help='Timeout in minutes for node commissioning and hardware configuration'),
cfg.IntOpt('apply_node_networking', default=5, help='Timeout in minutes for configuring node networking'),

View File

@ -39,7 +39,8 @@ class NodeDriver(ProviderDriver):
hd_fields.OrchestratorAction.ApplyNodeStorage,
hd_fields.OrchestratorAction.ApplyNodePlatform,
hd_fields.OrchestratorAction.DeployNode,
hd_fields.OrchestratorAction.DestroyNode]
hd_fields.OrchestratorAction.DestroyNode,
hd_fields.OrchestratorAction.ConfigureUserCredentials]
def execute_task(self, task_id):
task = self.state_manager.get_task(task_id)

View File

@ -33,6 +33,7 @@ import drydock_provisioner.drivers.node.maasdriver.models.vlan as maas_vlan
import drydock_provisioner.drivers.node.maasdriver.models.subnet as maas_subnet
import drydock_provisioner.drivers.node.maasdriver.models.machine as maas_machine
import drydock_provisioner.drivers.node.maasdriver.models.tag as maas_tag
import drydock_provisioner.drivers.node.maasdriver.models.sshkey as maas_keys
class MaasNodeDriver(NodeDriver):
maasdriver_options = [
@ -156,6 +157,41 @@ class MaasNodeDriver(NodeDriver):
status=hd_fields.TaskStatus.Complete,
result=subtask.get_result())
return
elif task.action == hd_fields.OrchestratorAction.ConfigureUserCredentials:
self.orchestrator.task_field_update(task.get_id(), status=hd_fields.TaskStatus.Running)
subtask = self.orchestrator.create_task(task_model.DriverTask,
parent_task_id=task.get_id(), design_id=design_id,
action=task.action, site_name=task.site_name,
task_scope={'site': task.site_name})
runner = MaasTaskRunner(state_manager=self.state_manager,
orchestrator=self.orchestrator,
task_id=subtask.get_id())
self.logger.info("Starting thread for task %s to configure user credentials" % (subtask.get_id()))
runner.start()
runner.join(timeout=config.conf.timeouts.configure_user_credentials * 60)
if runner.is_alive():
result = {
'retry': False,
'detail': 'MaaS ssh key creation timed-out'
}
self.logger.warning("Thread for task %s timed out after 120s" % (subtask.get_id()))
self.orchestrator.task_field_update(task.get_id(),
status=hd_fields.TaskStatus.Complete,
result=hd_fields.ActionResult.Failure,
result_detail=result)
else:
subtask = self.state_manager.get_task(subtask.get_id())
self.logger.info("Thread for task %s completed - result %s" % (subtask.get_id(), subtask.get_result()))
self.orchestrator.task_field_update(task.get_id(),
status=hd_fields.TaskStatus.Complete,
result=subtask.get_result())
return
elif task.action == hd_fields.OrchestratorAction.IdentifyNode:
self.orchestrator.task_field_update(task.get_id(),
@ -762,6 +798,50 @@ class MaasTaskRunner(drivers.DriverTaskRunner):
status=hd_fields.TaskStatus.Complete,
result=action_result,
result_detail=result_detail)
elif task_action == hd_fields.OrchestratorAction.ConfigureUserCredentials:
try:
key_list = maas_keys.SshKeys(self.maas_client)
key_list.refresh()
except:
self.orchestrator.task_field_update(self.task.get_id(),
status=hd_fields.TaskStatus.Complete,
result=hd_fields.ActionResult.Failure,
result_detail={'detail': 'Error accessing MaaS SshKeys API', 'retry': True})
return
site_model = site_design.get_site()
result_detail = { 'detail': [] }
failed = worked = False
for k in getattr(site_model, 'authorized_keys', []):
try:
if len(key_list.query({'key': k.replace("\n","")})) == 0:
new_key = maas_keys.SshKey(self.maas_client, key=k)
new_key = key_list.add(new_key)
self.logger.debug("Added SSH key %s to MaaS user profile. Will be installed on all deployed nodes." %
(k[:16]))
result_detail['detail'].append("Added SSH key %s" % (k[:16]))
worked = True
else:
self.logger.debug("SSH key %s already exists in MaaS user profile." % k[:16])
result_detail['detail'].append("SSH key %s alreayd exists" % (k[:16]))
worked = True
except Exception as ex:
self.logger.warning("Error adding SSH key to MaaS user profile: %s" % str(ex))
result_detail['detail'].append("Failed to add SSH key %s" % (k[:16]))
failed = True
if worked and failed:
final_result = hd_fields.ActionResult.PartialSuccess
elif worked:
final_result = hd_fields.ActionResult.Success
else:
final_result = hd_fields.ActionResult.Failure
self.orchestrator.task_field_update(self.task.get_id(),
status=hd_fields.TaskStatus.Complete,
result=final_result, result_detail=result_detail)
return
elif task_action == hd_fields.OrchestratorAction.IdentifyNode:
try:
machine_list = maas_machine.Machines(self.maas_client)

View File

@ -197,7 +197,7 @@ class ResourceCollectionBase(object):
resp = self.api_client.post(url, files=data_dict)
if resp.status_code == 200:
if resp.status_code in [200,201]:
resp_json = resp.json()
res.set_resource_id(resp_json.get('id'))
return res

View File

@ -113,6 +113,10 @@ class YamlIngester(IngesterPlugin):
'NodeTagDefinition: %s' % (self.definition_type))
model.tag_definitions.append(tag_model)
auth_keys = spec.get('authorized_keys', [])
model.authorized_keys = [k for k in auth_keys]
models.append(model)
else:
raise ValueError('Unknown API version %s of Region kind' %s (api_version))
@ -330,10 +334,8 @@ class YamlIngester(IngesterPlugin):
node_metadata = spec.get('metadata', {})
metadata_tags = node_metadata.get('tags', [])
model.tags = []
for t in metadata_tags:
model.tags.append(t)
model.tags = [t for t in metadata_tags]
owner_data = node_metadata.get('owner_data', {})
model.owner_data = {}

View File

@ -43,6 +43,7 @@ class OrchestratorAction(BaseDrydockEnum):
CreateNetworkTemplate = 'create_network_template'
CreateStorageTemplate = 'create_storage_template'
CreateBootMedia = 'create_boot_media'
ConfigureUserCredentials = 'configure_user_credentials'
PrepareHardwareConfig = 'prepare_hardware_config'
IdentifyNode = 'identify_node'
ConfigureHardware = 'configure_hardware'

View File

@ -37,6 +37,7 @@ class Site(base.DrydockPersistentObject, base.DrydockObject):
'tag_definitions': ovo_fields.ObjectField('NodeTagDefinitionList',
nullable=True),
'repositories': ovo_fields.ObjectField('RepositoryList', nullable=True),
'authorized_keys': ovo_fields.ListOfStringsField(nullable=True),
}
def __init__(self, **kwargs):
@ -51,6 +52,9 @@ class Site(base.DrydockPersistentObject, base.DrydockObject):
def add_tag_definition(self, tag_definition):
self.tag_definitions.append(tag_definition)
def add_key(self, key_string):
self.authorized_keys.append(key_string)
@base.DrydockObjectRegistry.register
class NodeTagDefinition(base.DrydockObject):

View File

@ -162,23 +162,60 @@ class Orchestrator(object):
'site': task.site
}
driver_task = self.create_task(tasks.DriverTask,
worked = failed = False
site_network_task = self.create_task(tasks.DriverTask,
parent_task_id=task.get_id(),
design_id=design_id,
task_scope=task_scope,
action=hd_fields.OrchestratorAction.CreateNetworkTemplate)
self.logger.info("Starting node driver task %s to create network templates" % (driver_task.get_id()))
self.logger.info("Starting node driver task %s to create network templates" % (site_network_task.get_id()))
driver.execute_task(driver_task.get_id())
driver.execute_task(site_network_task.get_id())
driver_task = self.state_manager.get_task(driver_task.get_id())
site_network_task = self.state_manager.get_task(site_network_task.get_id())
self.logger.info("Node driver task %s complete" % (driver_task.get_id()))
if site_network_task.get_result() in [hd_fields.ActionResult.Success,
hd_fields.ActionResult.PartialSuccess]:
worked = True
if site_network_task.get_result() in [hd_fields.ActionResult.Failure,
hd_fields.ActionResult.PartialSuccess]:
failed = True
self.logger.info("Node driver task %s complete" % (site_network_task.get_id()))
user_creds_task = self.create_task(tasks.DriverTask,
parent_task_id=task.get_id(),
design_id=design_id,
task_scope=task_scope,
action=hd_fields.OrchestratorAction.ConfigureUserCredentials)
self.logger.info("Starting node driver task %s to configure user credentials" % (user_creds_task.get_id()))
driver.execute_task(user_creds_task.get_id())
self.logger.info("Node driver task %s complete" % (site_network_task.get_id()))
user_creds_task = self.state_manager.get_task(site_network_task.get_id())
if user_creds_task.get_result() in [hd_fields.ActionResult.Success,
hd_fields.ActionResult.PartialSuccess]:
worked = True
if user_creds_task.get_result() in [hd_fields.ActionResult.Failure,
hd_fields.ActionResult.PartialSuccess]:
failed = True
if worked and failed:
final_result = hd_fields.ActionResult.PartialSuccess
elif worked:
final_result = hd_fields.ActionResult.Success
else:
final_result = hd_fields.ActionResult.Failure
self.task_field_update(task_id,
status=hd_fields.TaskStatus.Complete,
result=driver_task.get_result())
result=final_result)
return
elif task.action == hd_fields.OrchestratorAction.VerifyNode:
self.task_field_update(task_id,

View File

@ -33,6 +33,11 @@ spec:
# Image and package repositories needed by Drydock drivers. Needs to be defined
repositories:
- name: 'ubuntu-main'
authorized_keys:
- |
valid ssh key string
- |
valid ssh key string
---
apiVersion: 'v1.0'
kind: NetworkLink