DRYD-31 Drydock driven by a config file

Config file support via oslo_config
Example config file in examples/drydock.conf
This commit is contained in:
Scott Hussey 2017-06-19 16:14:35 -05:00
parent fbbb6b5186
commit 28d460590c
12 changed files with 177 additions and 79 deletions

View File

@ -10,4 +10,9 @@
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# limitations under the License.
from .config import DrydockConfig
config_mgr = DrydockConfig()
conf = config_mgr.conf

View File

@ -12,31 +12,55 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Read application configuration
#
# configuration map with defaults
from oslo_config import cfg
class DrydockConfig(object):
"""
Initialize all the core options
"""
# Core/General options
logging_options = [
cfg.StrOpt('log_level', default='INFO', help='Global log level for Drydock'),
cfg.StrOpt('global_logger_name', default='drydock', help='Logger name for the top-level logger'),
cfg.StrOpt('oobdriver_logger_name', default='${global_logger_name}.oobdriver'),
cfg.StrOpt('nodedriver_logger_name', default='${global_logger_name}.nodedriver'),
cfg.StrOpt('control_logger_name', default='${global_logger_name}.control'),
]
global_config = {
'log_level': 'DEBUG',
}
# API Authentication options
auth_options = [
cfg.StrOpt('admin_token', default='bigboss', help='X-Auth-Token value to bypass backend authentication'),
cfg.BoolOpt('bypass_enabled', default=False, help='Can backend authentication be bypassed?'),
]
node_driver = {
'maasdriver': {
},
}
# Enabled plugins
plugin_options = [
cfg.MultiStrOpt('ingester',
default=['drydock_provisioner.ingester.plugins.yaml.YamlIngester'],
help='Module path string of a input ingester to enable'),
cfg.MultiStrOpt('oob_driver',
default=['drydock_provisioner.drivers.oob.pyghmi_driver.PyghmiDriver'],
help='Module path string of a OOB driver to enable'),
cfg.StrOpt('node_driver',
default='drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver',
help='Module path string of the Node driver to enable'),
cfg.StrOpt('network_driver',
default=None, help='Module path string of the Network driver to enable'),
]
ingester_config = {
'plugins': ['drydock_provisioner.ingester.plugins.yaml.YamlIngester'],
}
# Timeouts for various tasks specified in minutes
timeout_options = [
cfg.IntOpt('create_network_template',default=2,help='Timeout in minutes for creating site network templates'),
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'),
cfg.IntOpt('deploy_node',default=45,help='Timeout in minutes for deploying a node'),
]
orchestrator_config = {
'drivers': {
'oob': 'drydock_provisioner.drivers.oob.pyghmi_driver.PyghmiDriver',
'node': 'drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver',
}
}
def __init__(self):
self.conf = cfg.ConfigOpts()
self.conf.register_opts(DrydockConfig.logging_options, group='logging')
self.conf.register_opts(DrydockConfig.auth_options, group='authentication')
self.conf.register_opts(DrydockConfig.plugin_options, group='plugins')
self.conf.register_opts(DrydockConfig.timeout_options, group='timeouts')

View File

@ -16,7 +16,7 @@ import falcon
import logging
import uuid
import drydock_provisioner.config as config
import drydock_provisioner
class AuthMiddleware(object):
@ -66,7 +66,7 @@ class ContextMiddleware(object):
requested_logging = req.get_header('X-Log-Level')
if (config.DrydockConfig.global_config.get('log_level', '') == 'DEBUG' or
if (drydock_provisioner.conf.logging.log_level == 'DEBUG' or
(requested_logging == 'DEBUG' and 'admin' in ctx.roles)):
ctx.set_log_level('DEBUG')
elif requested_logging == 'INFO':
@ -78,7 +78,7 @@ class ContextMiddleware(object):
class LoggingMiddleware(object):
def __init__(self):
self.logger = logging.getLogger('drydock.control')
self.logger = logging.getLogger(drydock_provisioner.conf.logging.control_logger_name)
def process_response(self, req, resp, resource, req_succeeded):
ctx = req.context

View File

@ -35,7 +35,7 @@ class ProviderDriver(object):
raise ValueError("ProviderDriver requires valid state manager")
self.state_manager = state_manager
# These are the actions that this driver supports
self.supported_actions = [hd_fields.OrchestratorAction.Noop]

View File

@ -43,6 +43,8 @@ class MaasRequestFactory(object):
def __init__(self, base_url, apikey):
self.base_url = base_url
self.apikey = apikey
print("Creating MaaS API client for URL %s with key %s" % (base_url, apikey))
self.signer = MaasOauth(apikey)
self.http_session = requests.Session()

View File

@ -16,8 +16,10 @@ import logging
import traceback
import sys
from oslo_config import cfg
import drydock_provisioner
import drydock_provisioner.error as errors
import drydock_provisioner.config as config
import drydock_provisioner.drivers as drivers
import drydock_provisioner.objects.fields as hd_fields
import drydock_provisioner.objects.task as task_model
@ -32,6 +34,11 @@ import drydock_provisioner.drivers.node.maasdriver.models.machine as maas_machin
class MaasNodeDriver(NodeDriver):
maasdriver_options = [
cfg.StrOpt('maas_api_key', help='The API key for accessing MaaS'),
cfg.StrOpt('maas_api_url', help='The URL for accessing MaaS API'),
]
def __init__(self, **kwargs):
super(MaasNodeDriver, self).__init__(**kwargs)
@ -39,9 +46,13 @@ class MaasNodeDriver(NodeDriver):
self.driver_key = "maasdriver"
self.driver_desc = "MaaS Node Provisioning Driver"
self.config = config.DrydockConfig.node_driver[self.driver_key]
self.setup_config_options(drydock_provisioner.conf)
self.logger = logging.getLogger('drydock.nodedriver.maasdriver')
self.logger = logging.getLogger("%s.%s" %
(drydock_provisioner.conf.logging.nodedriver_logger_name, self.driver_key))
def setup_config_options(self, conf):
conf.register_opts(MaasNodeDriver.maasdriver_options, group=self.driver_key)
def execute_task(self, task_id):
task = self.state_manager.get_task(task_id)
@ -56,7 +67,7 @@ class MaasNodeDriver(NodeDriver):
if task.action == hd_fields.OrchestratorAction.ValidateNodeServices:
self.orchestrator.task_field_update(task.get_id(),
status=hd_fields.TaskStatus.Running)
maas_client = MaasRequestFactory(self.config['api_url'], self.config['api_key'])
maas_client = MaasRequestFactory(drydock_provisioner.conf.maasdriver.maas_api_url, drydock_provisioner.conf.maasdriver.maas_api_key)
try:
if maas_client.test_connectivity():
@ -122,16 +133,13 @@ class MaasNodeDriver(NodeDriver):
task_scope={'site': task.site_name})
runner = MaasTaskRunner(state_manager=self.state_manager,
orchestrator=self.orchestrator,
task_id=subtask.get_id(),config=self.config)
task_id=subtask.get_id())
self.logger.info("Starting thread for task %s to create network templates" % (subtask.get_id()))
runner.start()
# TODO Figure out coherent system for putting all the timeouts in
# the config
runner.join(timeout=120)
runner.join(timeout=drydock_provisioner.conf.timeouts.create_network_template * 60)
if runner.is_alive():
result = {
@ -174,7 +182,7 @@ class MaasNodeDriver(NodeDriver):
task_scope={'site': task.site_name, 'node_names': [n]})
runner = MaasTaskRunner(state_manager=self.state_manager,
orchestrator=self.orchestrator,
task_id=subtask.get_id(),config=self.config)
task_id=subtask.get_id())
self.logger.info("Starting thread for task %s to identify node %s" % (subtask.get_id(), n))
@ -185,8 +193,7 @@ class MaasNodeDriver(NodeDriver):
attempts = 0
worked = failed = False
#TODO Add timeout to config
while running_subtasks > 0 and attempts < 3:
while running_subtasks > 0 and attempts < drydock_provisioner.conf.timeouts.identify_node:
for t in subtasks:
subtask = self.state_manager.get_task(t)
@ -244,7 +251,7 @@ class MaasNodeDriver(NodeDriver):
task_scope={'site': task.site_name, 'node_names': [n]})
runner = MaasTaskRunner(state_manager=self.state_manager,
orchestrator=self.orchestrator,
task_id=subtask.get_id(),config=self.config)
task_id=subtask.get_id())
self.logger.info("Starting thread for task %s to commission node %s" % (subtask.get_id(), n))
@ -256,7 +263,7 @@ class MaasNodeDriver(NodeDriver):
worked = failed = False
#TODO Add timeout to config
while running_subtasks > 0 and attempts < 20:
while running_subtasks > 0 and attempts < drydock_provisioner.conf.timeouts.configure_hardware:
for t in subtasks:
subtask = self.state_manager.get_task(t)
@ -314,7 +321,7 @@ class MaasNodeDriver(NodeDriver):
task_scope={'site': task.site_name, 'node_names': [n]})
runner = MaasTaskRunner(state_manager=self.state_manager,
orchestrator=self.orchestrator,
task_id=subtask.get_id(),config=self.config)
task_id=subtask.get_id())
self.logger.info("Starting thread for task %s to configure networking on node %s" % (subtask.get_id(), n))
@ -325,8 +332,7 @@ class MaasNodeDriver(NodeDriver):
attempts = 0
worked = failed = False
#TODO Add timeout to config
while running_subtasks > 0 and attempts < 2:
while running_subtasks > 0 and attempts < drydock_provisioner.conf.timeouts.apply_node_networking:
for t in subtasks:
subtask = self.state_manager.get_task(t)
@ -384,7 +390,7 @@ class MaasNodeDriver(NodeDriver):
task_scope={'site': task.site_name, 'node_names': [n]})
runner = MaasTaskRunner(state_manager=self.state_manager,
orchestrator=self.orchestrator,
task_id=subtask.get_id(),config=self.config)
task_id=subtask.get_id())
self.logger.info("Starting thread for task %s to deploy node %s" % (subtask.get_id(), n))
@ -395,8 +401,7 @@ class MaasNodeDriver(NodeDriver):
attempts = 0
worked = failed = False
#TODO Add timeout to config
while running_subtasks > 0 and attempts < 120:
while running_subtasks > 0 and attempts < drydock_provisioner.conf.timeouts.deploy_node:
for t in subtasks:
subtask = self.state_manager.get_task(t)
@ -435,10 +440,10 @@ class MaasNodeDriver(NodeDriver):
class MaasTaskRunner(drivers.DriverTaskRunner):
def __init__(self, config=None, **kwargs):
def __init__(self, **kwargs):
super(MaasTaskRunner, self).__init__(**kwargs)
self.driver_config = config
# TODO Need to build this name from configs
self.logger = logging.getLogger('drydock.nodedriver.maasdriver')
def execute_task(self):
@ -448,8 +453,8 @@ class MaasTaskRunner(drivers.DriverTaskRunner):
status=hd_fields.TaskStatus.Running,
result=hd_fields.ActionResult.Incomplete)
self.maas_client = MaasRequestFactory(self.driver_config['api_url'],
self.driver_config['api_key'])
self.maas_client = MaasRequestFactory(drydock_provisioner.conf.maasdriver.maas_api_url,
drydock_provisioner.conf.maasdriver.maas_api_key)
site_design = self.orchestrator.get_effective_site(self.task.design_id)
@ -738,7 +743,7 @@ class MaasTaskRunner(drivers.DriverTaskRunner):
# Poll machine status
attempts = 0
while attempts < 30 and machine.status_name != 'Ready':
while attempts < drydock_provisioner.conf.timeouts.configure_hardware and machine.status_name != 'Ready':
attempts = attempts + 1
time.sleep(1 * 60)
try:
@ -970,7 +975,7 @@ class MaasTaskRunner(drivers.DriverTaskRunner):
continue
attempts = 0
while attempts < 120 and not machine.status_name.startswith('Deployed'):
while attempts < drydock_provisioner.conf.timeouts.deploy_node and not machine.status_name.startswith('Deployed'):
attempts = attempts + 1
time.sleep(1 * 60)
try:

View File

@ -14,8 +14,9 @@
import time
import logging
import drydock_provisioner
import drydock_provisioner.error as errors
import drydock_provisioner.config as config
import drydock_provisioner.objects.fields as hd_fields
import drydock_provisioner.objects.task as task_model
@ -35,8 +36,8 @@ class ManualDriver(oob.OobDriver):
self.driver_key = "manual_driver"
self.driver_desc = "Manual (Noop) OOB Driver"
self.logger = logging.getLogger('drydock.oobdriver.pyghmi')
self.config = config.DrydockConfig.node_driver.get(self.driver_key, {})
self.logger = logging.getLogger("%s.%s" %
(drydock_provisioner.conf.logging.oobdriver_logger_name, self.driver_key))
def execute_task(self, task_id):
task = self.state_manager.get_task(task_id)

View File

@ -16,8 +16,8 @@ import logging
from pyghmi.ipmi.command import Command
import drydock_provisioner
import drydock_provisioner.error as errors
import drydock_provisioner.config as config
import drydock_provisioner.objects.fields as hd_fields
import drydock_provisioner.objects.task as task_model
@ -37,8 +37,8 @@ class PyghmiDriver(oob.OobDriver):
self.driver_key = "pyghmi_driver"
self.driver_desc = "Pyghmi OOB Driver"
self.logger = logging.getLogger('drydock.oobdriver.pyghmi')
self.config = config.DrydockConfig.node_driver.get(self.driver_key, {})
self.logger = logging.getLogger("%s.%s" %
(drydock_provisioner.conf.logging.oobdriver_logger_name, self.driver_key))
def execute_task(self, task_id):
task = self.state_manager.get_task(task_id)
@ -99,20 +99,16 @@ class PyghmiDriver(oob.OobDriver):
task_id=subtask.get_id(), node=n)
runner.start()
# Wait for subtasks to complete
# TODO need some kind of timeout
i = 0
while len(incomplete_subtasks) > 0:
attempts = 0
while len(incomplete_subtasks) > 0 and attempts <= getattr(drydock_provisioner.conf.timeouts, task.action, 5):
for n in incomplete_subtasks:
t = self.state_manager.get_task(n)
if t.get_status() in [hd_fields.TaskStatus.Terminated,
hd_fields.TaskStatus.Complete,
hd_fields.TaskStatus.Errored]:
incomplete_subtasks.remove(n)
time.sleep(2)
i = i+1
if i == 5:
break
time.sleep(1 * 60)
attempts = attempts + 1
task = self.state_manager.get_task(task.get_id())
subtasks = map(self.state_manager.get_task, task.get_subtasks())

View File

@ -12,8 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from oslo_config import cfg
import sys
import drydock_provisioner.config as config
import drydock_provisioner
import drydock_provisioner.objects as objects
import drydock_provisioner.ingester as ingester
import drydock_provisioner.statemgmt as statemgmt
@ -22,18 +24,29 @@ import drydock_provisioner.control.api as api
def start_drydock():
objects.register_all()
# Setup root logger
logger = logging.getLogger('drydock')
logger.setLevel(config.DrydockConfig.global_config.get('log_level'))
# Setup configuration parsing
cli_options = [
cfg.BoolOpt('debug', short='d', default=False, help='Enable debug logging'),
]
drydock_provisioner.conf.register_cli_opts(cli_options)
drydock_provisioner.conf(sys.argv[1:])
if drydock_provisioner.conf.debug:
drydock_provisioner.conf.logging.log_level = 'DEBUG'
# Setup root logger
logger = logging.getLogger(drydock_provisioner.conf.logging.global_logger_name)
logger.setLevel(drydock_provisioner.conf.logging.log_level)
ch = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s:%(funcName)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
# Specalized format for API logging
logger = logging.getLogger('drydock.control')
logger = logging.getLogger(drydock_provisioner.conf.logging.control_logger_name)
logger.propagate = False
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(user)s - %(req_id)s - %(external_ctx)s - %(message)s')
@ -43,10 +56,10 @@ def start_drydock():
state = statemgmt.DesignState()
orchestrator = orch.Orchestrator(config.DrydockConfig.orchestrator_config.get('drivers', {}),
orchestrator = orch.Orchestrator(drydock_provisioner.conf.plugins,
state_manager=state)
input_ingester = ingester.Ingester()
input_ingester.enable_plugins(config.DrydockConfig.ingester_config.get('plugins', []))
input_ingester.enable_plugins(drydock_provisioner.conf.plugins.ingester)
return api.start_api(state_manager=state, ingester=input_ingester,
orchestrator=orchestrator)

View File

@ -37,9 +37,14 @@ class Orchestrator(object):
self.logger = logging.getLogger('drydock.orchestrator')
if enabled_drivers is not None:
oob_drivers = enabled_drivers.get('oob', [])
oob_drivers = enabled_drivers.oob_driver
# This is because oslo_config changes the option value
# for multiopt depending on if multiple values are actually defined
print("%s" % (oob_drivers))
for d in oob_drivers:
print("Enabling OOB driver %s" % d)
if d is not None:
m, c = d.rsplit('.', 1)
oob_driver_class = \
@ -50,7 +55,7 @@ class Orchestrator(object):
self.enabled_drivers['oob'].append(oob_driver_class(state_manager=state_manager,
orchestrator=self))
node_driver_name = enabled_drivers.get('node', None)
node_driver_name = enabled_drivers.node_driver
if node_driver_name is not None:
m, c = node_driver_name.rsplit('.', 1)
node_driver_class = \
@ -59,7 +64,7 @@ class Orchestrator(object):
self.enabled_drivers['node'] = node_driver_class(state_manager=state_manager,
orchestrator=self)
network_driver_name = enabled_drivers.get('network', None)
network_driver_name = enabled_drivers.network_driver
if network_driver_name is not None:
m, c = network_driver_name.rsplit('.', 1)
network_driver_class = \

46
examples/drydock.conf Normal file
View File

@ -0,0 +1,46 @@
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
[DEFAULT]
# No global options yet
[logging]
log_level = 'DEBUG'
[authentication]
bypass_enabled = True
[plugins]
# All the config ingesters that are active
# Supports multiple values
ingester = 'drydock_provisioner.ingester.plugins.yaml.YamlIngester'
# OOB drivers that are enabled
# Supports multiple values
oob_driver = 'drydock_provisioner.drivers.oob.pyghmi_driver.PyghmiDriver'
oob_driver = 'drydock_provisioner.drivers.oob.manual_driver.driver.ManualDriver'
# Node driver that is enabled
node_driver = 'drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver'
[timeouts]
create_network_template = 2
identify_node = 10
configure_hardware = 30
apply_node_networking = 5
deploy_node = 45
[maasdriver]
maas_api_url = 'http://localhost:8000/MAAS/api/2.0/'
maas_api_key = 'your:secret:key'

View File

@ -63,7 +63,8 @@ setup(name='drydock_provisioner',
'requests',
'oauthlib',
'uwsgi>1.4',
'bson===0.4.7'
'bson===0.4.7',
'oslo.config',
]
)