diff --git a/drydock_provisioner/__init__.py b/drydock_provisioner/__init__.py index 2a385a45..6eda855f 100644 --- a/drydock_provisioner/__init__.py +++ b/drydock_provisioner/__init__.py @@ -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. \ No newline at end of file +# limitations under the License. + +from .config import DrydockConfig + +config_mgr = DrydockConfig() +conf = config_mgr.conf \ No newline at end of file diff --git a/drydock_provisioner/config.py b/drydock_provisioner/config.py index 112635a4..87228672 100644 --- a/drydock_provisioner/config.py +++ b/drydock_provisioner/config.py @@ -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', - } - } \ No newline at end of file + 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') diff --git a/drydock_provisioner/control/middleware.py b/drydock_provisioner/control/middleware.py index 28144c3b..00b807d9 100644 --- a/drydock_provisioner/control/middleware.py +++ b/drydock_provisioner/control/middleware.py @@ -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 diff --git a/drydock_provisioner/drivers/__init__.py b/drydock_provisioner/drivers/__init__.py index c3868af1..ec609412 100644 --- a/drydock_provisioner/drivers/__init__.py +++ b/drydock_provisioner/drivers/__init__.py @@ -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] diff --git a/drydock_provisioner/drivers/node/maasdriver/api_client.py b/drydock_provisioner/drivers/node/maasdriver/api_client.py index 01930a4e..789349b6 100644 --- a/drydock_provisioner/drivers/node/maasdriver/api_client.py +++ b/drydock_provisioner/drivers/node/maasdriver/api_client.py @@ -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() diff --git a/drydock_provisioner/drivers/node/maasdriver/driver.py b/drydock_provisioner/drivers/node/maasdriver/driver.py index 4984527d..1439b348 100644 --- a/drydock_provisioner/drivers/node/maasdriver/driver.py +++ b/drydock_provisioner/drivers/node/maasdriver/driver.py @@ -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: diff --git a/drydock_provisioner/drivers/oob/manual_driver/driver.py b/drydock_provisioner/drivers/oob/manual_driver/driver.py index e53f9e30..6e483aef 100644 --- a/drydock_provisioner/drivers/oob/manual_driver/driver.py +++ b/drydock_provisioner/drivers/oob/manual_driver/driver.py @@ -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) diff --git a/drydock_provisioner/drivers/oob/pyghmi_driver/__init__.py b/drydock_provisioner/drivers/oob/pyghmi_driver/__init__.py index 655241ac..5e9b3ee0 100644 --- a/drydock_provisioner/drivers/oob/pyghmi_driver/__init__.py +++ b/drydock_provisioner/drivers/oob/pyghmi_driver/__init__.py @@ -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()) diff --git a/drydock_provisioner/drydock.py b/drydock_provisioner/drydock.py index 18e53a13..06259816 100644 --- a/drydock_provisioner/drydock.py +++ b/drydock_provisioner/drydock.py @@ -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) diff --git a/drydock_provisioner/orchestrator/__init__.py b/drydock_provisioner/orchestrator/__init__.py index 8d86cff9..c639d8d0 100644 --- a/drydock_provisioner/orchestrator/__init__.py +++ b/drydock_provisioner/orchestrator/__init__.py @@ -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 = \ diff --git a/examples/drydock.conf b/examples/drydock.conf new file mode 100644 index 00000000..48db0e8d --- /dev/null +++ b/examples/drydock.conf @@ -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' \ No newline at end of file diff --git a/setup.py b/setup.py index 3aa5c1ae..bca84cf4 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,8 @@ setup(name='drydock_provisioner', 'requests', 'oauthlib', 'uwsgi>1.4', - 'bson===0.4.7' + 'bson===0.4.7', + 'oslo.config', ] )