diff --git a/osha/common/__init__.py b/osha/common/__init__.py index e69de29..0db0533 100644 --- a/osha/common/__init__.py +++ b/osha/common/__init__.py @@ -0,0 +1,15 @@ +"""Common module for osha.""" + +# (c) Copyright 2016 Hewlett-Packard Development Company, L.P. +# +# 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. diff --git a/osha/common/config.py b/osha/common/config.py index 414dd9d..7d90add 100644 --- a/osha/common/config.py +++ b/osha/common/config.py @@ -1,4 +1,6 @@ -# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P. +"""Manage all configuration the OpenStack way.""" + +# (c) Copyright 2016 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,12 +13,15 @@ # 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. -from oslo_config import cfg -from osha.common.utils import env import sys -from osha import __version__ as OSHA_VERSION + +from oslo_config import cfg from oslo_log import log +from osha import __version__ as OSHA_VERSION +from osha.common.utils import env + + CONF = cfg.CONF _MONITORS = [ @@ -32,11 +37,10 @@ _MONITORS = [ cfg.DictOpt('kwargs', default={}, help='List of kwargs if you want to pass it to initialize' - ' the monitoring driver. should be provided in key:value ' - 'format') + ' the monitoring driver. should be provided in key:value ' + 'format') ] - _COMMON = [ cfg.IntOpt('wait', default=30, @@ -46,93 +50,92 @@ _COMMON = [ _FENCER = [ cfg.StrOpt('credentials-file', help='YAML File contains the required credentials for compute ' - 'nodes'), + 'nodes'), cfg.IntOpt('retries', default=1, help='Number of retries to fence the each compute node. Must be' - ' at least 1 to try first the soft shutdown'), + ' at least 1 to try first the soft shutdown'), cfg.IntOpt('hold-period', default=10, help='Time in seconds to wait between retries. Should be ' - 'reasonable amount of time as different servers take ' - 'different times to shut off'), + 'reasonable amount of time as different servers take ' + 'different times to shut off'), cfg.StrOpt('driver', default='osha.fencers.drivers.ipmi.driver.IpmiDriver', help='Choose the best fencer driver i.e.(ipmi, libvirt, ..'), cfg.DictOpt('options', default={}, help='List of kwargs to customize the fencer operation. You ' - 'fencer driver should support these options. Options ' - 'should be in key:value format') + 'fencer driver should support these options. Options ' + 'should be in key:value format') ] _KEYSTONE_AUTH_TOKEN = [ cfg.StrOpt('auth_uri', help='Openstack auth URI i.e. http://controller:5000', - dest='auth_uri'), - cfg.StrOpt('auth_url', - help='Openstack auth URL i.e. http://controller:35357/v3', - dest='auth_url'), + dest='auth_uri'), cfg.StrOpt( + 'auth_url', + help='Openstack auth URL i.e. http://controller:35357/v3', + dest='auth_url'), cfg.StrOpt('auth_plugin', help='Openstack auth plugin i.e. ( password, token, ...) ' - 'password is the only available plugin for the time being', - dest='auth_plugin'), - cfg.StrOpt('username', - help='Openstack username', - dest='username'), + 'password is the only available plugin for the time being', + dest='auth_plugin'), cfg.StrOpt('username', + help='Openstack username', + dest='username'), cfg.StrOpt('password', help='Openstack Password', - dest='password'), - cfg.StrOpt('project_name', - help='Openstack Project Name.', - dest='project_name'), + dest='password'), cfg.StrOpt('project_name', + help='Openstack Project Name.', + dest='project_name'), cfg.StrOpt('domain_name', help='Openstack domain Name.', - dest='domain_name'), - cfg.StrOpt('project_domain_id', - help='Openstack Project Domain id, default is Default', - dest='project_domain_id'), + dest='domain_name'), cfg.StrOpt( + 'project_domain_id', + help='Openstack Project Domain id, default is Default', + dest='project_domain_id'), cfg.StrOpt('user_domain_id', help='Openstack user Domain id, default is Default', - dest='user_domain_id'), - cfg.StrOpt('project_domain_name', - help='Openstack Project Domain name, default is Default', - dest='project_domain_name'), - cfg.StrOpt('user_domain_name', - help='Openstack user Domain name, default is Default', - dest='user_domain_name'), + dest='user_domain_id'), cfg.StrOpt( + 'project_domain_name', + help='Openstack Project Domain name, default is Default', + dest='project_domain_name'), cfg.StrOpt( + 'user_domain_name', + help='Openstack user Domain name, default is Default', + dest='user_domain_name'), cfg.DictOpt('kwargs', help='Openstack Authentication arguments you can pass it here ' - 'as Key:Value, Key1:Value1, ... ', + 'as Key:Value, Key1:Value1, ... ', dest='kwargs', default={}) ] - _EVACUATION = [ - cfg.StrOpt('driver', - default='osha.evacuators.drivers.osha.standard.' - 'OshaStandardEvacuator', - help='Time in seconds to wait between retries to disable compute' - ' node or put it in maintenance mode. Default 10 seconds', - dest='driver'), - cfg.IntOpt('wait', - default=10, - help='Time in seconds to wait between retries to disable compute' - ' node or put it in maintenance mode. Default 10 seconds', - dest='wait'), + cfg.StrOpt( + 'driver', + default='osha.evacuators.drivers.osha.standard.' + 'OshaStandardEvacuator', + help='Time in seconds to wait between retries to disable compute' + ' node or put it in maintenance mode. Default 10 seconds', + dest='driver'), cfg.IntOpt( + 'wait', + default=10, + help='Time in seconds to wait between retries to disable compute' + ' node or put it in maintenance mode. Default 10 seconds', + dest='wait'), cfg.IntOpt('retries', default=1, help='Number of retries to put node in maintenance mode before ' - 'reporting failure to evacuate the node', + 'reporting failure to evacuate the node', dest='retries'), - cfg.DictOpt('options', - default={}, - help='Dict contains kwargs to be passed to the evacuator driver' - '. In case you have additional args needs to be passed to ' - 'your evacuator please, list them as key0:value0, ' - 'key1:value1, ....', - dest='options') + cfg.DictOpt( + 'options', + default={}, + help='Dict contains kwargs to be passed to the evacuator driver' + '. In case you have additional args needs to be passed to ' + 'your evacuator please, list them as key0:value0, ' + 'key1:value1, ....', + dest='options') ] _NOTIFIERS = [ @@ -140,121 +143,124 @@ _NOTIFIERS = [ default='osha.notifiers.drivers.osha.default_email.OshaEmail', dest='driver', help='Notification driver to load it to notify users ' - 'if something went wrong'), + 'if something went wrong'), cfg.StrOpt('endpoint', default=None, dest='endpoint', help='Endpoint URL for the notification system. If you the ' - 'driver you are using doesnot require any URL just comment ' - 'it or use none'), - cfg.StrOpt('username', - default=None, - dest='username', - help='Username to authenticate against the notification system. ' - 'If the driver you are using doesnot require any ' - 'authentications comment or use None'), - cfg.StrOpt('password', - default=None, - dest='password', - help='Password to authenticate against the notification system. ' - 'If the driver you are using doesnot require any ' - 'authentications comment or use None'), + 'driver you are using doesnot require any URL just comment ' + 'it or use none'), + cfg.StrOpt( + 'username', + default=None, + dest='username', + help='Username to authenticate against the notification system. ' + 'If the driver you are using doesnot require any ' + 'authentications comment or use None'), cfg.StrOpt( + 'password', + default=None, + dest='password', + help='Password to authenticate against the notification system. ' + 'If the driver you are using doesnot require any ' + 'authentications comment or use None'), cfg.StrOpt('templates-dir', dest='templates-dir', default='/etc/osha/templates', help='Path to Jinja2 templates directory that contains ' - 'message templates'), + 'message templates'), cfg.DictOpt('options', default={}, dest='options', help='Key:Value Kwargs to pass it to the notification driver, ' - 'if you want to pass any special arguments for your ' - 'driver. '), + 'if you want to pass any special arguments for your ' + 'driver. '), cfg.ListOpt('notify-list', default=[], dest='notify-list', help='List of emails to sent them notification if something ' - 'went wrong and Osha wasnot able to send an email to the ' - 'tenant admin'), + 'went wrong and Osha wasnot able to send an email to the ' + 'tenant admin'), cfg.StrOpt('notify-from', dest='notify-from', help='The sender address, it can be email address if we used ' - 'default email driver, or phone number if we use sms ' - 'gateway for example.') + 'default email driver, or phone number if we use sms ' + 'gateway for example.') ] def build_os_options(): + """Build oslo options related to OpenStack environment.""" osclient_opts = [ cfg.StrOpt('os-username', default=env('OS_USERNAME'), help='Name used for authentication with the OpenStack ' - 'Identity service. Defaults to env[OS_USERNAME].', + 'Identity service. Defaults to env[OS_USERNAME].', dest='os_username'), cfg.StrOpt('os-password', default=env('OS_PASSWORD'), help='Password used for authentication with the OpenStack ' - 'Identity service. Defaults to env[OS_PASSWORD].', + 'Identity service. Defaults to env[OS_PASSWORD].', dest='os_password'), cfg.StrOpt('os-project-name', default=env('OS_PROJECT_NAME'), help='Project name to scope to. Defaults to ' - 'env[OS_PROJECT_NAME].', + 'env[OS_PROJECT_NAME].', dest='os_project_name'), cfg.StrOpt('os-project-domain-name', default=env('OS_PROJECT_DOMAIN_NAME'), help='Domain name containing project. Defaults to ' - 'env[OS_PROJECT_DOMAIN_NAME].', + 'env[OS_PROJECT_DOMAIN_NAME].', dest='os_project_domain_name'), cfg.StrOpt('os-user-domain-name', default=env('OS_USER_DOMAIN_NAME'), help='User\'s domain name. Defaults to ' - 'env[OS_USER_DOMAIN_NAME].', + 'env[OS_USER_DOMAIN_NAME].', dest='os_user_domain_name'), cfg.StrOpt('os-tenant-name', default=env('OS_TENANT_NAME'), help='Tenant to request authorization on. Defaults to ' - 'env[OS_TENANT_NAME].', + 'env[OS_TENANT_NAME].', dest='os_tenant_name'), cfg.StrOpt('os-tenant-id', default=env('OS_TENANT_ID'), help='Tenant to request authorization on. Defaults to ' - 'env[OS_TENANT_ID].', + 'env[OS_TENANT_ID].', dest='os_tenant_id'), cfg.StrOpt('os-auth-url', default=env('OS_AUTH_URL'), help='Specify the Identity endpoint to use for ' - 'authentication. Defaults to env[OS_AUTH_URL].', + 'authentication. Defaults to env[OS_AUTH_URL].', dest='os_auth_url'), cfg.StrOpt('os-backup-url', default=env('OS_BACKUP_URL'), help='Specify the Freezer backup service endpoint to use. ' - 'Defaults to env[OS_BACKUP_URL].', + 'Defaults to env[OS_BACKUP_URL].', dest='os_backup_url'), cfg.StrOpt('os-region-name', default=env('OS_REGION_NAME'), help='Specify the region to use. Defaults to ' - 'env[OS_REGION_NAME].', + 'env[OS_REGION_NAME].', dest='os_region_name'), - cfg.StrOpt('os-token', - default=env('OS_TOKEN'), - help='Specify an existing token to use instead of retrieving' - ' one via authentication (e.g. with username & ' - 'password). Defaults to env[OS_TOKEN].', - dest='os_token'), + cfg.StrOpt( + 'os-token', + default=env('OS_TOKEN'), + help='Specify an existing token to use instead of retrieving' + ' one via authentication (e.g. with username & ' + 'password). Defaults to env[OS_TOKEN].', + dest='os_token'), cfg.StrOpt('os-identity-api-version', default=env('OS_IDENTITY_API_VERSION'), help='Identity API version: 2.0 or 3. ' - 'Defaults to env[OS_IDENTITY_API_VERSION]', + 'Defaults to env[OS_IDENTITY_API_VERSION]', dest='os_identity_api_version'), cfg.StrOpt('os-endpoint-type', choices=['public', 'publicURL', 'internal', 'internalURL', 'admin', 'adminURL'], default=env('OS_ENDPOINT_TYPE') or 'public', help='Endpoint type to select. Valid endpoint types: ' - '"public" or "publicURL", "internal" or "internalURL",' - ' "admin" or "adminURL". Defaults to ' - 'env[OS_ENDPOINT_TYPE] or "public"', + '"public" or "publicURL", "internal" or "internalURL",' + ' "admin" or "adminURL". Defaults to ' + 'env[OS_ENDPOINT_TYPE] or "public"', dest='os_endpoint_type'), ] @@ -262,19 +268,20 @@ def build_os_options(): def configure(): + """Register configuration.""" CONF.register_cli_opts(build_os_options()) CONF.register_opts(_COMMON) monitors_grp = cfg.OptGroup('monitoring', title='Monitoring', help='Monitoring Driver/plugin to be used to ' - 'monitor compute nodes') + 'monitor compute nodes') CONF.register_group(monitors_grp) CONF.register_opts(_MONITORS, group='monitoring') fencers_grp = cfg.OptGroup('fencer', - title='fencer Options', - help='fencer Driver/plugin to be used to ' - 'fence compute nodes') + title='fencer Options', + help='fencer Driver/plugin to be used to ' + 'fence compute nodes') CONF.register_group(fencers_grp) CONF.register_opts(_FENCER, group='fencer') @@ -282,16 +289,16 @@ def configure(): evacuators_grp = cfg.OptGroup('evacuation', title='Evacuation Options', help='Evacuation Driver/plugin opts to be ' - 'used to Evacuate compute nodes') + 'used to Evacuate compute nodes') CONF.register_group(evacuators_grp) CONF.register_opts(_EVACUATION, group='evacuation') # Notification Section :) notifiers_grp = cfg.OptGroup('notifiers', - title='Notification Options', - help='Notification Driver/plugin opts to be ' - 'used to Notify admins/users if failure ' - 'happens') + title='Notification Options', + help='Notification Driver/plugin opts to be ' + 'used to Notify admins/users if failure ' + 'happens') CONF.register_group(notifiers_grp) CONF.register_opts(_NOTIFIERS, group='notifiers') @@ -299,32 +306,33 @@ def configure(): keystone_grp = cfg.OptGroup('keystone_authtoken', title='Keystone Auth Options', help='Openstack Credentials to call the nova ' - 'APIs to evacuate ') + 'APIs to evacuate ') CONF.register_group(keystone_grp) CONF.register_opts(_KEYSTONE_AUTH_TOKEN, group='keystone_authtoken') - default_conf = cfg.find_config_files('osha', 'osha', - '.conf') + default_conf = cfg.find_config_files('osha', 'osha', '.conf') log.register_options(CONF) CONF(args=sys.argv[1:], project='osha', default_config_files=default_conf, - version=OSHA_VERSION - ) + version=OSHA_VERSION) def setup_logging(): - _DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'boto=WARN','qpid=WARN', - 'stevedore=WARN', 'oslo_log=INFO', 'iso8601=WARN', + """Set some oslo log defaults.""" + _DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'boto=WARN', + 'qpid=WARN', 'stevedore=WARN', 'oslo_log=INFO', + 'iso8601=WARN', 'requests.packages.urllib3.connectionpool=WARN', 'urllib3.connectionpool=WARN', 'websocket=WARN', 'keystonemiddleware=WARN', 'osha=INFO'] - _DEFAULT_LOGGING_CONTEXT_FORMAT = ('%(asctime)s.%(msecs)03d %(process)d ' - '%(levelname)s %(name)s [%(request_id)s ' - '%(user_identity)s] %(instance)s' - '%(message)s') + _DEFAULT_LOGGING_CONTEXT_FORMAT = ( + '%(asctime)s.%(msecs)03d %(process)d ' + '%(levelname)s %(name)s [%(request_id)s ' + '%(user_identity)s] %(instance)s' + '%(message)s') log.set_defaults(_DEFAULT_LOGGING_CONTEXT_FORMAT, _DEFAULT_LOG_LEVELS) log.setup(CONF, 'osha', version=OSHA_VERSION) @@ -340,4 +348,3 @@ def list_opts(): } return _OPTS.items() - diff --git a/osha/common/daemon.py b/osha/common/daemon.py index 6cbe11c..e620313 100644 --- a/osha/common/daemon.py +++ b/osha/common/daemon.py @@ -1,165 +1,183 @@ #!/usr/bin/env python - -import sys, os, time, atexit -from signal import SIGTERM +"""Generic deamon.""" +# (c) Copyright 2016 Hewlett-Packard Development Company, L.P. +# +# 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. + +from __future__ import print_function + +import atexit import logging as log +import os +import sys +import time - -class Daemon: +from signal import SIGTERM + + +class Daemon(object): + + """A generic daemon class. + + Usage: subclass the Daemon class and override the run() method + """ + + def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', + stderr='/dev/null'): + """Instantiantion.""" + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """Do the UNIX double-fork magic. + + See Stevens' "Advanced Programming in the UNIX Environment" for + details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 """ - A generic daemon class. - - Usage: subclass the Daemon class and override the run() method + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #1 failed: %d (%s)\n" % + (e.errno, e.strerror)) + log.error(e) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #2 failed: %d (%s)\n" + % (e.errno, e.strerror)) + log.error(e) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = file(self.stdin, 'r') + so = file(self.stdout, 'a+') + se = file(self.stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + atexit.register(self.delpid) + + pid = str(os.getpid()) + f = file(self.pidfile, 'w+') + f.write("%s\n" % pid) + f.close() + + def delpid(self): + """Delete PID file.""" + os.remove(self.pidfile) + + def start(self): + """Start the daemon.""" + log.error("Test") + # Check for a pidfile to see if the daemon already runs + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile %s already exist. Daemon" \ + " already running?\n" + sys.stderr.write(message % self.pidfile) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run() + + # @todo needs some enhancement like check /proc/%pid/status if it's + # really running or not ! may be it's killed by external process + # the PID won't be updated ! + def status(self): + """Check daemon status.""" + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile %s already exist. Daemon already " \ + "running. PID: %d \n" + sys.stdout.write(message % (self.pidfile, pid)) + sys.exit(0) + else: + message = "Service not running!\n" + sys.stdout.write(message) + sys.exit(0) + + def stop(self): + """Stop the daemon.""" + # Get the pid from the pidfile + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if not pid: + message = "pidfile %s does not exist." \ + " Daemon not running?\n" + sys.stderr.write(message % self.pidfile) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, SIGTERM) + time.sleep(0.1) + except OSError as err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print(str(err)) + sys.exit(1) + + def restart(self): + """Restart the daemon.""" + self.stop() + self.start() + + def run(self): + """You should override this method when you subclass Daemon. + + It will be called after the process has been + daemonized by start() or restart(). """ - def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', - stderr='/dev/null'): - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - self.pidfile = pidfile - - def daemonize(self): - """ - do the UNIX double-fork magic, see Stevens' "Advanced - Programming in the UNIX Environment" for details - (ISBN 0201563177) - http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 - """ - try: - pid = os.fork() - if pid > 0: - # exit first parent - sys.exit(0) - except OSError, e: - sys.stderr.write("fork #1 failed: %d (%s)\n" % - (e.errno, e.strerror)) - log.error(e) - sys.exit(1) - - # decouple from parent environment - os.chdir("/") - os.setsid() - os.umask(0) - - # do second fork - try: - pid = os.fork() - if pid > 0: - # exit from second parent - sys.exit(0) - except OSError, e: - sys.stderr.write("fork #2 failed: %d (%s)\n" - % (e.errno, e.strerror)) - log.error(e) - sys.exit(1) - - # redirect standard file descriptors - sys.stdout.flush() - sys.stderr.flush() - si = file(self.stdin, 'r') - so = file(self.stdout, 'a+') - se = file(self.stderr, 'a+', 0) - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - - # write pidfile - atexit.register(self.delpid) - - pid = str(os.getpid()) - f = file(self.pidfile, 'w+') - f.write("%s\n" % pid) - f.close() - - def delpid(self): - os.remove(self.pidfile) - - def start(self): - """ - Start the daemon - """ - log.error("Test") - # Check for a pidfile to see if the daemon already runs - try: - pf = file(self.pidfile,'r') - pid = int(pf.read().strip()) - pf.close() - except IOError as e: - pid = None - - if pid: - message = "pidfile %s already exist. Daemon" \ - " already running?\n" - sys.stderr.write(message % self.pidfile) - sys.exit(1) - - # Start the daemon - self.daemonize() - self.run() - - # @todo needs some enhancement like check /proc/%pid/status if it's - # really running or not ! may be it's killed by external process - # the PID won't be updated ! - def status(self): - try: - pf = file(self.pidfile, 'r') - pid = int(pf.read().strip()) - pf.close() - except IOError as e: - pid = None - - if pid: - message = "pidfile %s already exist. Daemon already " \ - "running. PID: %d \n" - sys.stdout.write(message % (self.pidfile, pid)) - sys.exit(0) - else: - message = "Service not running !\n" - sys.stdout.write(message) - sys.exit(0) - - def stop(self): - """ - Stop the daemon - """ - # Get the pid from the pidfile - try: - pf = file(self.pidfile,'r') - pid = int(pf.read().strip()) - pf.close() - except IOError: - pid = None - - if not pid: - message = "pidfile %s does not exist." \ - " Daemon not running?\n" - sys.stderr.write(message % self.pidfile) - return # not an error in a restart - - # Try killing the daemon process - try: - while 1: - os.kill(pid, SIGTERM) - time.sleep(0.1) - except OSError, err: - err = str(err) - if err.find("No such process") > 0: - if os.path.exists(self.pidfile): - os.remove(self.pidfile) - else: - print str(err) - sys.exit(1) - - def restart(self): - """ - Restart the daemon - """ - self.stop() - self.start() - - def run(self): - """ - You should override this method when you subclass Daemon. - It will be called after the process has been - daemonized by start() or restart(). - """ \ No newline at end of file diff --git a/osha/common/osclient.py b/osha/common/osclient.py index 9b56313..857d679 100644 --- a/osha/common/osclient.py +++ b/osha/common/osclient.py @@ -1,4 +1,5 @@ -# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P. +"""OpenStack client class.""" +# (c) Copyright 2016 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,21 +12,30 @@ # 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. +from __future__ import print_function + +from keystoneclient import session from keystoneclient.auth.identity import v3 -from keystoneclient import session -from novaclient.v2 import client as novaclient -from neutronclient.v2_0 import client as neutronclient + from keystoneclient.v3 import client as keystoneclient + +from neutronclient.v2_0 import client as neutronclient + +from novaclient.v2 import client as novaclient + from oslo_log import log + LOG = log.getLogger(__name__) class OSClient: + """Provide OpenStack credentials to initalize the connection.""" + def __init__(self, authurl, authmethod='password', ** kwargs): - """ - Provide Openstack credentials to initalize the connection to Openstack + """Initialize the all class vars. + :param authmethod: string authmethod should be password or token but currently we support only password ! :param kwargs: username, user_id, project_name, project_id, @@ -49,15 +59,17 @@ class OSClient: # self.user_id = kwargs.get('user_id', None) # self.user_domain_id = kwargs.get('user_domain_id', None) # self.user_domain_name = kwargs.get('user_domain_name', None) - # self.project_domain_name = kwargs.get('project_domain_name', None) + # self.project_domain_name = + # kwargs.get('project_domain_name', None) # self.endpoint_type = kwargs.get('endpoint_type', 'internalURL') else: - print "The available authmethod is password for the time being" \ - "Please, provide a password credentials :) " + print("The available authmethod is password for the time being") + print("Please, provide a password credential.") self.auth() def auth(self): + """Create a session.""" auth = v3.Password(auth_url=self.authurl, **self.kwargs) self.auth_session = session.Session(auth=auth) @@ -89,16 +101,16 @@ class OSClient: def neutronagents(self, hosts=[]): if not hosts: hosts = self.compute_hosts - new_sess = session.Session(auth=self.auth_session.auth) - neutron = neutronclient.Client(session=new_sess, + auth_session = session.Session(auth=self.auth_session.auth) + neutron = neutronclient.Client(session=auth_session, endpoint_type=self.endpoint_type) - self.auth_session = new_sess + self.auth_session = auth_session agents = neutron.list_agents() neutron_agents = [] for agent in agents.get('agents'): - if agent.get('host') in hosts and agent.get('binary') == \ - 'neutron-openvswitch-agent': - neutron_agents.append(agent) + if agent.get('host') in hosts and agent.get('binary') == \ + 'neutron-openvswitch-agent': + neutron_agents.append(agent) return neutron_agents @@ -109,10 +121,10 @@ class OSClient: :param nodes: List of nodes to be evacuated ! :return: List of nodes with VMs that were running on that node """ - new_sess = session.Session(auth=self.auth_session.auth) - nova = novaclient.Client(session=new_sess, + auth_session = session.Session(auth=self.auth_session.auth) + nova = novaclient.Client(session=auth_session, endpoint_type=self.endpoint_type) - self.auth_session = new_sess + self.auth_session = auth_session evacuated_nodes = [] for node in nodes: hypervisors = nova.hypervisors.search(node.get('host'), True) @@ -125,15 +137,17 @@ class OSClient: on_shared_storage=True) except Exception as e: LOG.error(e) - host = {'host': node.get('host'), 'servers': hypervisor.servers} + host = {'host': node.get( + 'host'), 'servers': hypervisor.servers} evacuated_nodes.append(host) return evacuated_nodes - def set_in_maintance(self, nodes): - new_sess = session.Session(auth=self.auth_session.auth) - nova = novaclient.Client(session=new_sess, + def set_in_maintenance(self, nodes): + """Set compute nodes in maintenance mode.""" + auth_session = session.Session(auth=self.auth_session.auth) + nova = novaclient.Client(session=auth_session, endpoint_type=self.endpoint_type) - self.auth_session = new_sess + self.auth_session = auth_session for node in nodes: output = [] host = nova.hosts.get(node)[0] @@ -145,12 +159,13 @@ class OSClient: return output def get_session(self): + """Get the authentication section.""" auth_session = session.Session(auth=self.auth_session.auth) return auth_session def get_node_status(self, node): - """ - Check the node nova-service status and if it's disabled or not + """Check the node nova-service status and if it's disabled or not. + :param node: dict contains node info :return: True or False. True => node disabled, False => node is enabled or unknow status ! @@ -172,6 +187,7 @@ class OSClient: return False def disable_node(self, node): + """Disable nova on the failing node.""" auth_session = session.Session(auth=self.auth_session.auth) nova = novaclient.Client(session=auth_session, endpoint_type=self.endpoint_type) @@ -189,7 +205,7 @@ class OSClient: nova.services.disable_log_reason( host=node.get('host'), binary=node.get('binary'), - reason='Host Failed and needs to be evacuated.' + reason='Host failed and needs to be evacuated.' ) del nova LOG.info('Compute host: %s has been disabled to be evacuated. ' @@ -200,6 +216,7 @@ class OSClient: return True def get_hypervisor_instances(self, node): + """Get instances from an hypervisor.""" auth_session = session.Session(auth=self.auth_session.auth) nova = novaclient.Client(session=auth_session, endpoint_type=self.endpoint_type) @@ -209,8 +226,8 @@ class OSClient: return hypervisors[0].servers def get_hypervisor(self, node): - """ - Get an instance of the hypervisor, so you can do any operation you want. + """Get an instance of the hypervisor. + :param node: dict contains host index :return: Hypervisor """ @@ -223,6 +240,7 @@ class OSClient: return hypervisors[0] def get_instances_list(self, node): + """Get instances running on a node for all tenants.""" auth_session = session.Session(auth=self.auth_session.auth) nova = novaclient.Client(session=auth_session, endpoint_type=self.endpoint_type) @@ -239,6 +257,7 @@ class OSClient: return self.get_instances_list(node) def list_tenants(self): + """List tenants.""" auth_session = session.Session(auth=self.auth_session.auth) keystone = keystoneclient.Client(session=auth_session, endpoint_type=self.endpoint_type) @@ -251,6 +270,7 @@ class OSClient: return projects_data def users_on_tenant(self, tenant): + """List user per project.""" auth_session = session.Session(auth=self.auth_session.auth) keystone = keystoneclient.Client(session=auth_session, endpoint_type=self.endpoint_type, @@ -259,7 +279,7 @@ class OSClient: try: users = keystone.users.list(default_project=tenant) except Exception as e: - print e + print(e) users_list = [] for user in users: users_list.append(user.to_dict()) @@ -267,6 +287,7 @@ class OSClient: return users_list def get_hypervisors_stats(self): + """Get stats for all hypervisors.""" auth_session = session.Session(auth=self.auth_session.auth) nova = novaclient.Client(session=auth_session, endpoint_type=self.endpoint_type) @@ -274,6 +295,7 @@ class OSClient: return stats.to_dict() def get_hypervisor_details(self, node): + """Get details about hypervisor running on the provided node.""" auth_session = session.Session(auth=self.auth_session.auth) nova = novaclient.Client(session=auth_session, endpoint_type=self.endpoint_type) diff --git a/osha/common/utils.py b/osha/common/utils.py index ff03c4b..5de8f3b 100644 --- a/osha/common/utils.py +++ b/osha/common/utils.py @@ -1,4 +1,5 @@ -# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P. +"""Utility functions shared from all modules into the project.""" +# (c) Copyright 2016 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,18 +12,22 @@ # 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. - import os -from osha.common.osclient import OSClient + +import jinja2 + from oslo_config import cfg from oslo_log import log -import jinja2 + +from osha.common.osclient import OSClient + CONF = cfg.CONF LOG = log.getLogger(__name__) def env(*env_vars, **kwargs): + """Get all environment variables.""" for variable in env_vars: value = os.environ.get(variable, None) if value: @@ -31,7 +36,8 @@ def env(*env_vars, **kwargs): def get_os_client(): - """ + """Return the OpenStack client. + Loads credentials from [keystone_authtoken] section in the configuration file and initialize the client and return an instance of the client :return: Initialized instance of OS Client @@ -53,14 +59,15 @@ def get_os_client(): def load_jinja_templates(template_dir, template_name, template_vars): - """ - Load and render existing Jinja2 templates. The main purpose of the function - is to prepare the message to be sent and render it for the driver to send - it directly + """Load and render existing Jinja2 templates. + + The main purpose of the function is to prepare the message to be sent and + render it for the driver to send it directly. + :param template_dir: Location where jinja2 templates are stored :param template_name: name of the template to load it :param template_vars: Dict to replace existing vars in the template with - values. + values. :return: String message """ template_loader = jinja2.FileSystemLoader(searchpath=template_dir) @@ -70,7 +77,8 @@ def load_jinja_templates(template_dir, template_name, template_vars): def get_admin_os_client(): - """ + """Return admin client data. + Loads credentials from [keystone_authtoken] section in the configuration file and initialize the client with admin privileges and return an instance of the client diff --git a/osha/fencers/common/driver.py b/osha/fencers/common/driver.py index ef184b3..5e33893 100644 --- a/osha/fencers/common/driver.py +++ b/osha/fencers/common/driver.py @@ -1,4 +1,4 @@ -# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P. +# (c) Copyright 2016 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,52 +12,58 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Abstract fencer""" + import abc + import six @six.add_metaclass(abc.ABCMeta) class FencerBaseDriver(object): - """ - Abstract class that all fencer plugins should implement to have a - unified interface and as many plugins as we want... + + """Abstract class that all fencer plugins. + + Should be implemented to have a unified interface and as many plugins as + needed. """ def __init__(self, node, **kwargs): - """ - Initializing the driver. Any fencer driver requires the following - parameters to do the api calls. All these parameters can be passed from - the configuration file in /etc/osha/osha.conf (default) + """Initialize the driver. + + Any fencer driver requires the following parameters to do the api + calls. All these parameters can be passed from the configuration + file in /etc/osha/osha.conf (default). + :param node: dict with all node details. (/etc/osha/servers.yml) ? - :param kwargs: any additional parameters can be passed using this config - option. + :param kwargs: any additional parameters can be passed using this + config option. """ self.node = node self.kwargs = kwargs @abc.abstractmethod def graceful_shutdown(self): - """ - Gracefully shutdown the compute node to evacuate it. - """ + """Gracefully shutdown the compute node to evacuate it.""" @abc.abstractmethod def force_shutdown(self): - """ - Force shutdown the compute node to evacuate it. May be you can try force - shutdown if the graceful shutdown failed + """Force shutdown the compute node to evacuate it. + + May be you can try force shutdown if the graceful shutdown failed. """ @abc.abstractmethod def status(self): - """ - Get compute node status. should return 1 if on and 0 if off or - -1 if error or unknown power status + """Get compute node status. + + Should return 1 if on and 0 if off or -1 if error or unknown power + status. """ @abc.abstractmethod def get_info(self): - """ - Get Driver information .. + """Get Driver information. + :return: dict of name, version, author, ... """