summaryrefslogtreecommitdiffstats
path: root/compass-tasks-base/actions
diff options
context:
space:
mode:
Diffstat (limited to 'compass-tasks-base/actions')
-rw-r--r--compass-tasks-base/actions/__init__.py13
-rw-r--r--compass-tasks-base/actions/clean.py195
-rw-r--r--compass-tasks-base/actions/cli.py179
-rw-r--r--compass-tasks-base/actions/delete.py148
-rw-r--r--compass-tasks-base/actions/deploy.py182
-rw-r--r--compass-tasks-base/actions/health_check/__init__.py13
-rw-r--r--compass-tasks-base/actions/health_check/base.py57
-rw-r--r--compass-tasks-base/actions/health_check/check.py96
-rw-r--r--compass-tasks-base/actions/health_check/check_apache.py89
-rw-r--r--compass-tasks-base/actions/health_check/check_celery.py115
-rw-r--r--compass-tasks-base/actions/health_check/check_dhcp.py184
-rw-r--r--compass-tasks-base/actions/health_check/check_dns.py139
-rw-r--r--compass-tasks-base/actions/health_check/check_hds.py97
-rw-r--r--compass-tasks-base/actions/health_check/check_misc.py219
-rw-r--r--compass-tasks-base/actions/health_check/check_os_installer.py151
-rw-r--r--compass-tasks-base/actions/health_check/check_package_installer.py68
-rw-r--r--compass-tasks-base/actions/health_check/check_squid.py128
-rw-r--r--compass-tasks-base/actions/health_check/check_tftp.py96
-rw-r--r--compass-tasks-base/actions/health_check/utils.py114
-rw-r--r--compass-tasks-base/actions/install_callback.py181
-rw-r--r--compass-tasks-base/actions/patch.py69
-rw-r--r--compass-tasks-base/actions/poll_switch.py162
-rw-r--r--compass-tasks-base/actions/reinstall.py38
-rw-r--r--compass-tasks-base/actions/search.py46
-rw-r--r--compass-tasks-base/actions/update_progress.py298
-rw-r--r--compass-tasks-base/actions/util.py342
26 files changed, 3419 insertions, 0 deletions
diff --git a/compass-tasks-base/actions/__init__.py b/compass-tasks-base/actions/__init__.py
new file mode 100644
index 0000000..4ee55a4
--- /dev/null
+++ b/compass-tasks-base/actions/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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/compass-tasks-base/actions/clean.py b/compass-tasks-base/actions/clean.py
new file mode 100644
index 0000000..a4e9bc9
--- /dev/null
+++ b/compass-tasks-base/actions/clean.py
@@ -0,0 +1,195 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to clean installers
+"""
+import logging
+import xmlrpclib
+
+from compass.actions import util
+
+try:
+ import chef
+except ImportError:
+ pass
+
+class CobblerInstaller(object):
+ """cobbler installer"""
+ CREDENTIALS = "credentials"
+ USERNAME = 'username'
+ PASSWORD = 'password'
+
+ INSTALLER_URL = "cobbler_url"
+
+ def __init__(self, settings):
+ username = settings[self.CREDENTIALS][self.USERNAME]
+ password = settings[self.CREDENTIALS][self.PASSWORD]
+ cobbler_url = settings[self.INSTALLER_URL]
+ try:
+ self.remote = xmlrpclib.Server(cobbler_url)
+ self.token = self.remote.login(username, password)
+ logging.info('cobbler %s client created', cobbler_url)
+ except Exception as error:
+ logging.error(
+ 'failed to login %s with (%s, %s)',
+ cobbler_url, username, password
+ )
+ logging.exception(error)
+
+ def clean(self):
+ systems = self.remote.get_systems()
+ for system in systems:
+ system_name = system['name']
+ try:
+ self.remote.remove_system(system_name, self.token)
+ logging.info('system %s is removed', system_name)
+ except Exception as error:
+ logging.error(
+ 'failed to remove system %s', system_name
+ )
+ logging.exception(error)
+
+
+class AnsibleInstaller(object):
+
+ def __init__(self, settings):
+ return
+
+ def clean(self):
+ pass
+
+
+class ChefInstaller(object):
+ DATABAGS = "databags"
+ CHEFSERVER_URL = "chef_url"
+ CHEFSERVER_DNS = "chef_server_dns"
+ CHEFSERVER_IP = "chef_server_ip"
+ KEY_DIR = "key_dir"
+ CLIENT = "client_name"
+
+ def __init__(self, settings):
+ installer_url = settings.get(self.CHEFSERVER_URL, None)
+ key_dir = settings.get(self.KEY_DIR, None)
+ client = settings.get(self.CLIENT, None)
+ try:
+ if installer_url and key_dir and client:
+ self.api = chef.ChefAPI(installer_url, key_dir, client)
+ else:
+ self.api = chef.autoconfigure()
+ logging.info(
+ 'chef client created %s(%s, %s)',
+ installer_url, key_dir, client
+ )
+ except Exception as error:
+ logging.error(
+ 'failed to create chef client %s(%s, %s)',
+ installer_url, key_dir, client
+ )
+ logging.exception(error)
+
+ def clean(self):
+ try:
+ for node_name in chef.Node.list(api=self.api):
+ node = chef.Node(node_name, api=self.api)
+ node.delete()
+ logging.info('delete node %s', node_name)
+ except Exception as error:
+ logging.error('failed to delete some nodes')
+ logging.exception(error)
+
+ try:
+ for client_name in chef.Client.list(api=self.api):
+ if client_name in ['chef-webui', 'chef-validator']:
+ continue
+ client = chef.Client(client_name, api=self.api)
+ client.delete()
+ logging.info('delete client %s', client_name)
+ except Exception as error:
+ logging.error('failed to delete some clients')
+ logging.exception(error)
+
+ try:
+ for env_name in chef.Environment.list(api=self.api):
+ if env_name == '_default':
+ continue
+ env = chef.Environment(env_name, api=self.api)
+ env.delete()
+ logging.info('delete env %s', env_name)
+ except Exception as error:
+ logging.error('failed to delete some envs')
+ logging.exception(error)
+
+ try:
+ for databag_name in chef.DataBag.list(api=self.api):
+ databag = chef.DataBag(databag_name, api=self.api)
+ for item_name, item in databag.items():
+ item.delete()
+ logging.info(
+ 'delete item %s from databag %s',
+ item_name, databag_name
+ )
+ except Exception as error:
+ logging.error('failed to delete some databag items')
+ logging.exception(error)
+
+
+OS_INSTALLERS = {
+ 'cobbler': CobblerInstaller
+}
+PK_INSTALLERS = {
+ 'chef_installer': ChefInstaller,
+ 'ansible_installer': AnsibleInstaller
+}
+
+
+def clean_os_installer(
+ os_installer_name, os_installer_settings
+):
+ with util.lock('serialized_action', timeout=100) as lock:
+ if not lock:
+ raise Exception(
+ 'failed to acquire lock to clean os installer'
+ )
+
+ if os_installer_name not in OS_INSTALLERS:
+ logging.error(
+ '%s not found in os_installers',
+ os_installer_name
+ )
+
+ os_installer = OS_INSTALLERS[os_installer_name](
+ os_installer_settings
+ )
+ os_installer.clean()
+
+
+def clean_package_installer(
+ package_installer_name, package_installer_settings
+):
+ with util.lock('serialized_action', timeout=100) as lock:
+ if not lock:
+ raise Exception(
+ 'failed to acquire lock to clean package installer'
+ )
+
+ if package_installer_name not in PK_INSTALLERS:
+ logging.error(
+ '%s not found in os_installers',
+ package_installer_name
+ )
+
+ package_installer = PK_INSTALLERS[package_installer_name](
+ package_installer_settings
+ )
+ package_installer.clean()
diff --git a/compass-tasks-base/actions/cli.py b/compass-tasks-base/actions/cli.py
new file mode 100644
index 0000000..c9058ed
--- /dev/null
+++ b/compass-tasks-base/actions/cli.py
@@ -0,0 +1,179 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Compass Command Line Interface"""
+import logging
+import subprocess
+import sys
+
+from compass.actions.health_check import check
+from compass.db.api import database
+
+from compass.utils import flags
+from compass.utils import logsetting
+from compass.utils import setting_wrapper as setting
+from compass.utils.util import pretty_print
+
+
+ACTION_MAP = {
+ "check": "apache celery dhcp dns hds misc os_installer "
+ "package_installer squid tftp".split(" "),
+ "refresh": "db sync".split(" "),
+}
+
+
+class BootCLI(object):
+ """CLI to do compass check."""
+
+ def __init__(self):
+ return
+
+ def run(self, args):
+ """cli takes the commands and calls respective modules."""
+ action = self.get_action(args)
+ if action is None:
+ self.print_help()
+ else:
+ module = self.get_module(action, args)
+ if module == "invalid":
+ self.print_help(action)
+ else:
+ method = "self.run_" + action + "(module)"
+ eval(method)
+
+ @classmethod
+ def get_action(cls, args):
+ """This method returns an action type.
+
+ .. note::
+ For 'compass check dhcp' command, it will return 'check'.
+ """
+ if len(args) == 1:
+ return None
+ elif args[1] in ACTION_MAP.keys():
+ return args[1]
+ return None
+
+ @classmethod
+ def get_module(cls, action, args):
+ """This method returns a module.
+
+ .. note::
+ For 'compass check dhcp' command, it will return 'dhcp'.
+ """
+ if len(args) <= 2:
+ return None
+ elif args[2] in ACTION_MAP[action]:
+ return args[2]
+ return "invalid"
+
+ def run_check(self, module=None):
+ """This provides a flexible sanity check.
+
+ .. note::
+ param module default set to None.
+ if parameter module is none. Compass checks all modules.
+ If module specified, Compass will only check such module.
+ """
+ if module is None:
+ pretty_print("Starting: Compass Health Check",
+ "==============================")
+ chk = check.BootCheck()
+ res = chk.run()
+ self.output_check_result(res)
+
+ else:
+ pretty_print("Checking Module: %s" % module,
+ "============================")
+ chk = check.BootCheck()
+ method = "chk._check_" + module + "()"
+ res = eval(method)
+ print "\n".join(msg for msg in res[1])
+
+ @classmethod
+ def output_check_result(cls, result):
+ """output check result."""
+ if result == {}:
+ return
+ pretty_print("\n",
+ "===============================",
+ "* Compass Health Check Report *",
+ "===============================")
+ successful = True
+ for key in result.keys():
+ if result[key][0] == 0:
+ successful = False
+ print "%s" % "\n".join(item for item in result[key][1])
+
+ print "===================="
+ if successful is True:
+ print "Compass Check completes. No problems found, all systems go"
+ sys.exit(0)
+ else:
+ print (
+ "Compass has ERRORS shown above. Please fix them before "
+ "deploying!")
+ sys.exit(1)
+
+ @classmethod
+ def run_refresh(cls, action=None):
+ """Run refresh."""
+ # TODO(xicheng): replace refresh.sh with refresh.py
+ if action is None:
+ pretty_print("Refreshing Compass...",
+ "=================")
+ subprocess.Popen(
+ ['/opt/compass/bin/refresh.sh'], shell=True)
+ elif action == "db":
+ pretty_print("Refreshing Compass Database...",
+ "===================")
+ subprocess.Popen(
+ ['/opt/compass/bin/manage_db.py createdb'], shell=True)
+ else:
+ pretty_print("Syncing with Installers...",
+ "================")
+ subprocess.Popen(
+ ['/opt/compass/bin/manage_db.py sync_from_installers'],
+ shell=True
+ )
+
+ @classmethod
+ def print_help(cls, module_help=""):
+ """print help."""
+ if module_help == "":
+ pretty_print("usage\n=====",
+ "compass <refresh|check>",
+ "type 'compass {action} --help' for detailed "
+ "command list")
+
+ elif module_help == "refresh":
+ pretty_print("usage\n=====",
+ "compass refresh [%s]" %
+ "|".join(action for action in ACTION_MAP['refresh']))
+
+ else:
+ pretty_print("usage\n=====",
+ "compass check [%s]" %
+ "|".join(action for action in ACTION_MAP['check']))
+ sys.exit(2)
+
+
+def main():
+ """Compass cli entry point."""
+ flags.init()
+ logsetting.init()
+ database.init()
+ cli = BootCLI()
+ output = cli.run(sys.argv)
+ return sys.exit(output)
diff --git a/compass-tasks-base/actions/delete.py b/compass-tasks-base/actions/delete.py
new file mode 100644
index 0000000..d89994d
--- /dev/null
+++ b/compass-tasks-base/actions/delete.py
@@ -0,0 +1,148 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to delete a given cluster
+"""
+import logging
+
+from compass.actions import util
+from compass.db.api import cluster as cluster_api
+from compass.db.api import host as host_api
+from compass.db.api import user as user_db
+from compass.deployment.deploy_manager import DeployManager
+from compass.deployment.utils import constants as const
+
+
+def delete_cluster(
+ cluster_id, host_id_list,
+ username=None, delete_underlying_host=False
+):
+ """Delete cluster and all clusterhosts on it.
+
+ :param cluster_id: id of the cluster.
+ :type cluster_id: int
+ :param host_id_list: list of host id.
+ :type host_id_list: list of int.
+
+ If delete_underlying_host is set, all underlying hosts will
+ be deleted.
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action', timeout=100) as lock:
+ if not lock:
+ raise Exception('failed to acquire lock to delete cluster')
+
+ user = user_db.get_user_object(username)
+
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, host_id_list, user)
+
+ deploy_manager = DeployManager(adapter_info, cluster_info, hosts_info)
+
+ deploy_manager.remove_hosts(
+ package_only=not delete_underlying_host,
+ delete_cluster=True
+ )
+ util.ActionHelper.delete_cluster(
+ cluster_id, host_id_list, user,
+ delete_underlying_host
+ )
+
+
+def delete_cluster_host(
+ cluster_id, host_id,
+ username=None, delete_underlying_host=False
+):
+ """Delete clusterhost.
+
+ :param cluster_id: id of the cluster.
+ :type cluster_id: int
+ :param host_id: id of the host.
+ :type host_id: int
+
+ If delete_underlying_host is set, the underlying host
+ will be deleted too.
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action', timeout=100) as lock:
+ if not lock:
+ raise Exception('failed to acquire lock to delete clusterhost')
+
+ user = user_db.get_user_object(username)
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, [host_id], user)
+
+ deploy_manager = DeployManager(adapter_info, cluster_info, hosts_info)
+
+ deploy_manager.remove_hosts(
+ package_only=not delete_underlying_host,
+ delete_cluster=False
+ )
+ util.ActionHelper.delete_cluster_host(
+ cluster_id, host_id, user,
+ delete_underlying_host
+ )
+
+
+def delete_host(
+ host_id, cluster_id_list, username=None
+):
+ """Delete host and all clusterhosts on it.
+
+ :param host_id: id of the host.
+ :type host_id: int
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action', timeout=100) as lock:
+ if not lock:
+ raise Exception('failed to acquire lock to delete host')
+
+ user = user_db.get_user_object(username)
+ for cluster_id in cluster_id_list:
+ cluster_info = util.ActionHelper.get_cluster_info(
+ cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, [host_id], user)
+
+ deploy_manager = DeployManager(
+ adapter_info, cluster_info, hosts_info)
+
+ deploy_manager.remove_hosts(
+ package_only=True,
+ delete_cluster=False
+ )
+
+ util.ActionHelper.delete_host(
+ host_id, user
+ )
diff --git a/compass-tasks-base/actions/deploy.py b/compass-tasks-base/actions/deploy.py
new file mode 100644
index 0000000..53179f5
--- /dev/null
+++ b/compass-tasks-base/actions/deploy.py
@@ -0,0 +1,182 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to deploy a given cluster
+"""
+import logging
+
+from compass.actions import util
+from compass.db.api import cluster as cluster_db
+from compass.db.api import health_check_report as health_check_db
+from compass.db.api import user as user_db
+from compass.deployment.deploy_manager import DeployManager
+from compass.deployment.utils import constants as const
+
+
+def deploy(cluster_id, hosts_id_list, username=None):
+ """Deploy clusters.
+
+ :param cluster_hosts: clusters and hosts in each cluster to deploy.
+ :type cluster_hosts: dict of int or str to list of int or str
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action', timeout=1000) as lock:
+ if not lock:
+ raise Exception('failed to acquire lock to deploy')
+
+ user = user_db.get_user_object(username)
+
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, hosts_id_list, user)
+
+ deploy_successful = True
+ try:
+ deploy_manager = DeployManager(
+ adapter_info, cluster_info, hosts_info)
+ # deploy_manager.prepare_for_deploy()
+ logging.debug('Created deploy manager with %s %s %s'
+ % (adapter_info, cluster_info, hosts_info))
+ deployed_config = deploy_manager.deploy()
+ except Exception as error:
+ logging.exception(error)
+ deploy_successful = False
+
+ if deploy_successful:
+ util.ActionHelper.save_deployed_config(deployed_config, user)
+ util.ActionHelper.update_state(
+ cluster_id, hosts_id_list, user, state='INSTALLING'
+ )
+ else:
+ util.ActionHelper.update_state(
+ cluster_id, hosts_id_list, user, state='ERROR',
+ message='failed to start deployment', severity='ERROR'
+ )
+
+
+def redeploy(cluster_id, username=None):
+ """Deploy clusters.
+
+ :param cluster_hosts: clusters and hosts in each cluster to deploy.
+ :type cluster_hosts: dict of int or str to list of int or str
+ """
+ with util.lock('serialized_action') as lock:
+ if not lock:
+ raise Exception('failed to acquire lock to deploy')
+
+ user = user_db.get_user_object(username)
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+
+ cluster_hosts = cluster_db.list_cluster_hosts(cluster_id, user)
+ hosts_id_list = [host['id'] for host in cluster_hosts]
+
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, hosts_id_list, user)
+
+ deploy_successful = True
+ try:
+ deploy_manager = DeployManager(
+ adapter_info, cluster_info, hosts_info)
+ # deploy_manager.prepare_for_deploy()
+ deploy_manager.redeploy()
+ except Exception as error:
+ logging.exception(error)
+ deploy_successful = False
+ if deploy_successful:
+ util.ActionHelper.update_state(
+ cluster_id, hosts_id_list, user, state='INSTALLING',
+ )
+ else:
+ util.ActionHelper.update_state(
+ cluster_id, hosts_id_list, user, state='ERROR',
+ message='failed to start redeployment', severity='ERROR'
+ )
+
+
+def health_check(cluster_id, report_uri, username):
+ with util.lock('cluster_health_check') as lock:
+ if not lock:
+ raise Exception('failed to acquire lock to check health')
+
+ user = user_db.get_user_object(username)
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user
+ )
+
+ deploy_manager = DeployManager(adapter_info, cluster_info, None)
+ try:
+ deploy_manager.check_cluster_health(report_uri)
+ except Exception as exc:
+ logging.error("health_check exception: ============= %s" % exc)
+ data = {'state': 'error', 'error_message': str(exc), 'report': {}}
+ reports = health_check_db.list_health_reports(
+ cluster_id, user=user)
+ if not reports:
+ # Exception before executing command remotely for health check.
+ # No reports names sending back yet. Create a report
+ name = 'pre_remote_health_check'
+ health_check_db.add_report_record(
+ cluster_id, name, user=user, **data
+ )
+
+ health_check_db.update_multi_reports(cluster_id, user=user, **data)
+
+
+class ServerPowerMgmt(object):
+ """Power management for bare-metal machines by IPMI command."""
+ @staticmethod
+ def poweron(machine_id, user):
+ """Power on the specified machine."""
+ pass
+
+ @staticmethod
+ def poweroff(machine_id, user):
+ pass
+
+ @staticmethod
+ def reset(machine_id, user):
+ pass
+
+
+class HostPowerMgmt(object):
+ """Power management for hosts installed OS by OS installer. OS installer
+
+ will poweron/poweroff/reset host.
+
+ """
+ @staticmethod
+ def poweron(host_id, user):
+ """Power on the specified host."""
+ pass
+
+ @staticmethod
+ def poweroff(host_id, user):
+ pass
+
+ @staticmethod
+ def reset(host_id, user):
+ pass
diff --git a/compass-tasks-base/actions/health_check/__init__.py b/compass-tasks-base/actions/health_check/__init__.py
new file mode 100644
index 0000000..4ee55a4
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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/compass-tasks-base/actions/health_check/base.py b/compass-tasks-base/actions/health_check/base.py
new file mode 100644
index 0000000..22b6fae
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/base.py
@@ -0,0 +1,57 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Base class for Compass Health Check."""
+from compass.actions.health_check import utils as health_check_utils
+from compass.db.api import adapter as adapter_api
+from compass.utils import setting_wrapper as setting
+
+
+class BaseCheck(object):
+ """health check base class."""
+
+ def __init__(self):
+ self.config = setting
+ self.code = 1
+ self.messages = []
+ self.dist, self.version, self.release = health_check_utils.get_dist()
+ adapter_api.load_adapters_internal()
+ self.os_installer = self._get_os_installer()
+ self.package_installer = self._get_package_installer()
+
+ def _get_os_installer(self):
+ installer = adapter_api.OS_INSTALLERS.values()[0]
+ os_installer = {}
+ os_installer['name'] = health_check_utils.strip_name(
+ installer['name'])
+ os_installer.update(installer['settings'])
+ return os_installer
+
+ def _get_package_installer(self):
+ package_installer = {}
+ installer = adapter_api.PACKAGE_INSTALLERS.values()[0]
+ package_installer = {}
+ package_installer['name'] = health_check_utils.strip_name(
+ installer['name'])
+ package_installer.update(installer['settings'])
+ return package_installer
+
+ def _set_status(self, code, message):
+ """set status."""
+ self.code = code
+ self.messages.append(message)
+
+ def get_status(self):
+ """get status."""
+ return (self.code, self.messages)
diff --git a/compass-tasks-base/actions/health_check/check.py b/compass-tasks-base/actions/health_check/check.py
new file mode 100644
index 0000000..c1adbc6
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check.py
@@ -0,0 +1,96 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Main Entry Point of Compass Health Check."""
+from compass.actions.health_check import base
+from compass.actions.health_check import check_apache
+from compass.actions.health_check import check_celery
+from compass.actions.health_check import check_dhcp
+from compass.actions.health_check import check_dns
+from compass.actions.health_check import check_hds
+from compass.actions.health_check import check_misc
+from compass.actions.health_check import check_os_installer
+from compass.actions.health_check import check_package_installer
+from compass.actions.health_check import check_squid
+from compass.actions.health_check import check_tftp
+
+
+class BootCheck(base.BaseCheck):
+ """health check for all components."""
+
+ def run(self):
+ """do health check."""
+ status = {}
+ status['apache'] = self._check_apache()
+ status['celery'] = self._check_celery()
+ status['dhcp'] = self._check_dhcp()
+ status['dns'] = self._check_dns()
+ status['hds'] = self._check_hds()
+ status['os_installer'] = self._check_os_installer()
+ status['package_installer'] = self._check_package_installer()
+ status['squid'] = self._check_squid()
+ status['tftp'] = self._check_tftp()
+ status['other'] = self._check_misc()
+
+ return status
+
+ def _check_apache(self):
+ """do apache health check."""
+ checker = check_apache.ApacheCheck()
+ return checker.run()
+
+ def _check_celery(self):
+ """do celery health check."""
+ checker = check_celery.CeleryCheck()
+ return checker.run()
+
+ def _check_dhcp(self):
+ """do dhcp health check."""
+ checker = check_dhcp.DhcpCheck()
+ return checker.run()
+
+ def _check_dns(self):
+ """do dns health check."""
+ checker = check_dns.DnsCheck()
+ return checker.run()
+
+ def _check_hds(self):
+ """do hds health check."""
+ checker = check_hds.HdsCheck()
+ return checker.run()
+
+ def _check_os_installer(self):
+ """do os installer health check."""
+ checker = check_os_installer.OsInstallerCheck()
+ return checker.run()
+
+ def _check_package_installer(self):
+ """do package installer health check."""
+ checker = check_package_installer.PackageInstallerCheck()
+ return checker.run()
+
+ def _check_squid(self):
+ """do squid health check."""
+ checker = check_squid.SquidCheck()
+ return checker.run()
+
+ def _check_tftp(self):
+ """do tftp health check."""
+ checker = check_tftp.TftpCheck()
+ return checker.run()
+
+ def _check_misc(self):
+ """do misc health check."""
+ checker = check_misc.MiscCheck()
+ return checker.run()
diff --git a/compass-tasks-base/actions/health_check/check_apache.py b/compass-tasks-base/actions/health_check/check_apache.py
new file mode 100644
index 0000000..294d6f9
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_apache.py
@@ -0,0 +1,89 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for Apache service."""
+
+import socket
+import urllib2
+
+from compass.actions.health_check import base
+from compass.actions.health_check import utils as health_check_utils
+
+
+class ApacheCheck(base.BaseCheck):
+ """apache server health check class."""
+ NAME = "Apache Check"
+
+ def run(self):
+ """do the healthcheck."""
+ if self.dist in ("centos", "redhat", "fedora", "scientific linux"):
+ apache_service = 'httpd'
+ else:
+ apache_service = 'apache2'
+ self.check_apache_conf(apache_service)
+ print "[Done]"
+ self.check_apache_running(apache_service)
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: Apache health check has completed. "
+ "No problems found, all systems go." % self.NAME)
+ return (self.code, self.messages)
+
+ def check_apache_conf(self, apache_service):
+ """Validates if Apache settings.
+
+ :param apache_service : service type of apache, os dependent.
+ e.g. httpd or apache2
+ :type apache_service : string
+
+ """
+ print "Checking Apache Config......",
+ conf_err_msg = health_check_utils.check_path(
+ self.NAME,
+ "/etc/%s/conf.d/ods-server.conf" % apache_service)
+ if not conf_err_msg == "":
+ self._set_status(0, conf_err_msg)
+
+ wsgi_err_msg = health_check_utils.check_path(
+ self.NAME,
+ '/var/www/compass/compass.wsgi')
+ if not wsgi_err_msg == "":
+ self._set_status(0, wsgi_err_msg)
+
+ return True
+
+ def check_apache_running(self, apache_service):
+ """Checks if Apache service is running on port 80."""
+
+ print "Checking Apache service......",
+ serv_err_msg = health_check_utils.check_service_running(self.NAME,
+ apache_service)
+ if not serv_err_msg == "":
+ self._set_status(0, serv_err_msg)
+ if 'http' != socket.getservbyport(80):
+ self._set_status(
+ 0,
+ "[%s]Error: Apache is not listening on port 80."
+ % self.NAME)
+ try:
+ html = urllib2.urlopen('http://localhost')
+ html.geturl()
+ except Exception:
+ self._set_status(
+ 0,
+ "[%s]Error: Apache is not listening on port 80."
+ % self.NAME)
+
+ return True
diff --git a/compass-tasks-base/actions/health_check/check_celery.py b/compass-tasks-base/actions/health_check/check_celery.py
new file mode 100644
index 0000000..2d8d27c
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_celery.py
@@ -0,0 +1,115 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for Celery."""
+import commands
+import os
+
+from celery.task.control import inspect
+
+from compass.actions.health_check import base
+from compass.actions.health_check import utils as health_check_utils
+
+
+class CeleryCheck(base.BaseCheck):
+ """celery health check class."""
+ NAME = "Celery Check."
+
+ def run(self):
+ """do health check."""
+ self.check_compass_celery_setting()
+ print "[Done]"
+ self.check_celery_backend()
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append("[%s]Info: Celery health check "
+ "has completed. No problems found, "
+ "all systems go." % self.NAME)
+ return (self.code, self.messages)
+
+ def check_compass_celery_setting(self):
+ """Validates Celery settings."""
+
+ print "Checking Celery setting......",
+ setting_map = {
+ 'logfile': 'CELERY_LOGFILE',
+ 'configdir': 'CELERYCONFIG_DIR',
+ 'configfile': 'CELERYCONFIG_FILE',
+ }
+ unset = []
+ res = health_check_utils.validate_setting('Celery',
+ self.config,
+ 'CELERY_LOGFILE')
+ if res is False:
+ unset.append(setting_map["logfile"])
+ self._set_status(0, res)
+
+ res = health_check_utils.validate_setting('Celery',
+ self.config,
+ 'CELERYCONFIG_DIR')
+ if res is False:
+ unset.append(setting_map["configdir"])
+ self._set_status(0, res)
+
+ res = health_check_utils.validate_setting('Celery',
+ self.config,
+ 'CELERYCONFIG_FILE')
+ if res is False:
+ unset.append(setting_map["configdir"])
+ self._set_status(0, res)
+
+ if len(unset) != 0:
+ self._set_status(0,
+ "[%s]Error: Unset celery settings: %s"
+ " in /etc/compass/setting"
+ % (self.NAME, ', '.join(item for item in unset)))
+ return True
+
+ def check_celery_backend(self):
+ """Checks if Celery backend is running and configured properly."""
+
+ print "Checking Celery Backend......",
+ if 'celery worker' not in commands.getoutput('ps -ef'):
+ self._set_status(0, "[%s]Error: celery is not running" % self.NAME)
+ return True
+
+ if not os.path.exists('/etc/compass/celeryconfig'):
+ self._set_status(
+ 0,
+ "[%s]Error: No celery config file found for Compass"
+ % self.NAME)
+ return True
+
+ try:
+ insp = inspect()
+ celery_stats = inspect.stats(insp)
+ if not celery_stats:
+ self._set_status(
+ 0,
+ "[%s]Error: No running Celery workers were found."
+ % self.NAME)
+ except IOError as error:
+ self._set_status(
+ 0,
+ "[%s]Error: Failed to connect to the backend: %s"
+ % (self.NAME, str(error)))
+ from errno import errorcode
+ if (
+ len(error.args) > 0 and
+ errorcode.get(error.args[0]) == 'ECONNREFUSED'
+ ):
+ self.messages.append(
+ "[%s]Error: RabbitMQ server isn't running"
+ % self.NAME)
+ return True
diff --git a/compass-tasks-base/actions/health_check/check_dhcp.py b/compass-tasks-base/actions/health_check/check_dhcp.py
new file mode 100644
index 0000000..e3bae1e
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_dhcp.py
@@ -0,0 +1,184 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for DHCP service."""
+import commands
+import os
+import re
+import socket
+import xmlrpclib
+
+from compass.actions.health_check import base
+
+
+class DhcpCheck(base.BaseCheck):
+ """dhcp health check class."""
+
+ NAME = "DHCP Check"
+
+ def run(self):
+ """do health check."""
+ method_name = "self.check_" + self.os_installer['name'] + "_dhcp()"
+ return eval(method_name)
+
+ def check_cobbler_dhcp(self):
+ """Checks if Cobbler has taken over DHCP service."""
+
+ try:
+ remote = xmlrpclib.Server(
+ self.os_installer['cobbler_url'],
+ allow_none=True)
+ credentials = self.os_installer['credentials']
+ remote.login(
+ credentials['username'], credentials['password'])
+ except Exception:
+ self._set_status(
+ 0,
+ "[%s]Error: Cannot login to Cobbler with "
+ "the tokens provided in the config file" % self.NAME)
+ return (self.code, self.messages)
+
+ cobbler_settings = remote.get_settings()
+ if cobbler_settings['manage_dhcp'] == 0:
+ self.messages.append(
+ "[%s]Info: DHCP service is "
+ "not managed by Compass" % self.NAME)
+ self.code = 0
+ return (self.code, self.messages)
+
+ self.check_cobbler_dhcp_template()
+ print "[Done]"
+ self.check_dhcp_service()
+ self.check_dhcp_netmask()
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: DHCP health check has completed. "
+ "No problems found, all systems go." % self.NAME)
+
+ return (self.code, self.messages)
+
+ def check_cobbler_dhcp_template(self):
+ """Validates Cobbler's DHCP template file."""
+ print "Checking DHCP template......",
+ if os.path.exists("/etc/cobbler/dhcp.template"):
+ var_map = {
+ "match_next_server": False,
+ "match_subnet": False,
+ "match_filename": False,
+ "match_range": False,
+ }
+
+ ip_regex = re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
+
+ dhcp_template = open("/etc/cobbler/dhcp.template")
+ for line in dhcp_template.readlines():
+ if line.find("next_server") != -1:
+ elmlist = line.split(" ")
+ for elm in elmlist:
+ if ";" in elm:
+ elm = elm[:-2]
+
+ if "$next_server" in elm or ip_regex.match(elm):
+ var_map["match_next_server"] = True
+
+ elif line.find("subnet") != -1 and line.find("{") != -1:
+ elmlist = line.split(" ")
+ for elm in elmlist:
+ if ip_regex.match(elm):
+ if elm[-1] == "0" and "255" not in elm:
+ var_map["match_subnet"] = True
+ elif elm[-1] != "0":
+ self.messages.append(
+ "[%s]Error: Subnet should be set "
+ "in the form of 192.168.0.0 in"
+ "/etc/cobbler/dhcp.template" % self.NAME)
+
+ elif line.find("filename") != -1:
+ var_map["match_filename"] = True
+ elif line.find("range dynamic-bootp") != -1:
+ elmlist = line.split(" ")
+ ip_count = 0
+ for elm in elmlist:
+ if ";" in elm and "\n" in elm:
+ elm = elm[:-2]
+
+ if ip_regex.match(elm):
+ ip_count += 1
+
+ if ip_count != 2:
+ self.messages.append(
+ "[%s]Error: DHCP range should be set "
+ "between two IP addresses in "
+ "/etc/cobbler/dhcp.template" % self.NAME)
+ else:
+ var_map["match_range"] = True
+
+ dhcp_template.close()
+ fails = []
+ for var in var_map.keys():
+ if var_map[var] is False:
+ fails.append(var)
+
+ if len(fails) != 0:
+ self._set_status(
+ 0,
+ "[%s]Info: DHCP template file "
+ "failed components: %s" % (
+ self.NAME, ' '.join(failed for failed in fails)))
+
+ else:
+ self._set_status(
+ 0,
+ "[%s]Error: DHCP template file doesn't exist, "
+ "health check failed." % self.NAME)
+
+ return True
+
+ def check_dhcp_netmask(self):
+ with open('/etc/dhcp/dhcpd.conf') as conf_reader:
+ lines = conf_reader.readlines()
+ for line in lines:
+ if re.search('^subnet', line):
+ elm_list = line.split(' ')
+ break
+ subnet_ip = elm_list[1]
+ netmask = elm_list[-2]
+ subnet_ip_elm = subnet_ip.split('.')
+ netmask_elm = netmask.split('.')
+ for index, digit in enumerate(subnet_ip_elm):
+ if int(digit) & int(netmask_elm[index]) != int(digit):
+ self._set_status(
+ 0,
+ "[%s]Info: DHCP subnet IP and "
+ "netmask do not match" % self.NAME)
+ break
+ return True
+
+ def check_dhcp_service(self):
+ """Checks if DHCP is running on port 67."""
+ print "Checking DHCP service......",
+ if not commands.getoutput('pgrep dhcp'):
+ self._set_status(
+ 0,
+ "[%s]Error: dhcp service does not "
+ "seem to be running" % self.NAME)
+
+ if socket.getservbyport(67) != 'bootps':
+ self._set_status(
+ 0,
+ "[%s]Error: bootps is not listening "
+ "on port 67" % self.NAME)
+
+ return True
diff --git a/compass-tasks-base/actions/health_check/check_dns.py b/compass-tasks-base/actions/health_check/check_dns.py
new file mode 100644
index 0000000..843d7e2
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_dns.py
@@ -0,0 +1,139 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for DNS service."""
+
+import commands
+import os
+import socket
+import xmlrpclib
+
+from compass.actions.health_check import base
+
+
+class DnsCheck(base.BaseCheck):
+ """dns health check class."""
+ NAME = "DNS Check"
+
+ def run(self):
+ """do health check."""
+ method_name = "self.check_" + self.os_installer['name'] + "_dns()"
+ return eval(method_name)
+
+ def check_cobbler_dns(self):
+ """Checks if Cobbler has taken over DNS service."""
+ try:
+ remote = xmlrpclib.Server(
+ self.os_installer['cobbler_url'],
+ allow_none=True)
+ credentials = self.os_installer['credentials']
+ remote.login(
+ credentials['username'], credentials['password'])
+ except Exception:
+ self._set_status(0,
+ "[%s]Error: Cannot login to Cobbler "
+ "with the tokens provided in the config file"
+ % self.NAME)
+ return (self.code, self.messages)
+
+ cobbler_settings = remote.get_settings()
+ if cobbler_settings['manage_dns'] == 0:
+ self.messages.append('[DNS]Info: DNS is not managed by Compass')
+ return (0, self.messages)
+ self.check_cobbler_dns_template()
+ print "[Done]"
+ self.check_dns_service()
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: DNS health check has complated. "
+ "No problems found, all systems go." % self.NAME)
+ return (self.code, self.messages)
+
+ def check_cobbler_dns_template(self):
+ """Validates Cobbler's DNS template file."""
+
+ print "Checking DNS template......",
+ if os.path.exists("/etc/cobbler/named.template"):
+ var_map = {
+ "match_port": False,
+ "match_allow_query": False,
+ }
+ named_template = open("/etc/cobbler/named.template")
+ host_ip = socket.gethostbyname(socket.gethostname())
+ missing_query = []
+ for line in named_template.readlines():
+ if "listen-on port 53" in line and host_ip in line:
+ var_map["match_port"] = True
+
+ if "allow-query" in line:
+ for subnet in ["127.0.0.0/8"]:
+ if subnet not in line:
+ missing_query.append(subnet)
+
+ named_template.close()
+
+ if var_map["match_port"] is False:
+ self.messages.append(
+ "[%s]Error: named service port "
+ "and/or IP is misconfigured in "
+ "/etc/cobbler/named.template" % self.NAME)
+
+ if len(missing_query) != 0:
+ self.messages.append(
+ "[%s]Error: Missing allow_query values in "
+ "/etc/cobbler/named.template:%s" % (
+ self.NAME,
+ ', '.join(subnet for subnet in missing_query)))
+ else:
+ var_map["match_allow_query"] = True
+
+ fails = []
+ for var in var_map.keys():
+ if var_map[var] is False:
+ fails.append(var)
+
+ if len(fails) != 0:
+ self._set_status(
+ 0,
+ "[%s]Info: DNS template failed components: "
+ "%s" % (
+ self.NAME,
+ ' '.join(failed for failed in fails)))
+
+ else:
+ self._set_status(
+ 0,
+ "[%s]Error: named template file doesn't exist, "
+ "health check failed." % self.NAME)
+
+ return True
+
+ def check_dns_service(self):
+ """Checks if DNS is running on port 53."""
+
+ print "Checking DNS service......",
+ if 'named' not in commands.getoutput('ps -ef'):
+ self._set_status(
+ 0,
+ "[%s]Error: named service does not seem to be "
+ "running" % self.NAME)
+
+ if socket.getservbyport(53) != 'domain':
+ self._set_status(
+ 0,
+ "[%s]Error: domain service is not listening on port "
+ "53" % self.NAME)
+
+ return None
diff --git a/compass-tasks-base/actions/health_check/check_hds.py b/compass-tasks-base/actions/health_check/check_hds.py
new file mode 100644
index 0000000..d176f1f
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_hds.py
@@ -0,0 +1,97 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for Hardware Discovery."""
+import logging
+
+from compass.actions.health_check import base
+from compass.actions.health_check import utils as health_check_utils
+
+
+class HdsCheck(base.BaseCheck):
+ """hds health check class."""
+ NAME = "HDS Check"
+
+ def run(self):
+ """do health check."""
+ if self.dist in ("centos", "redhat", "fedora", "scientific linux"):
+ pkg_type = "yum"
+ else:
+ pkg_type = "apt"
+
+ try:
+ pkg_module = __import__(pkg_type)
+ except Exception:
+ self._set_status(
+ 0, "[%s]Error: No module named %s please install it first."
+ % (self.NAME, pkg_type)
+ )
+ return (self.code, self.messages)
+
+ logging.info('import %s: %s', pkg_type, pkg_module)
+ method_name = 'self.check_' + pkg_type + '_snmp(pkg_module)'
+ eval(method_name)
+ print "[Done]"
+ self.check_snmp_mibs()
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append("[%s]Info: hds health check has complated. "
+ "No problems found, all systems go."
+ % self.NAME)
+
+ return (self.code, self.messages)
+
+ def check_yum_snmp(self, pkg_module):
+ """Check if SNMP yum dependencies are installed
+
+ :param pkg_module : python yum library
+ :type pkg_module : python module
+
+ """
+ print "Checking SNMP Packages......",
+ yum_base = pkg_module.YumBase()
+ uninstalled = []
+ for package in ['net-snmp-utils', 'net-snmp', 'net-snmp-python']:
+ if len(yum_base.rpmdb.searchNevra(name=package)) == 0:
+ self.messages.append("[%s]Error: %s package is required "
+ "for HDS" % (self.NAME, package))
+ uninstalled.append(package)
+
+ if len(uninstalled) != 0:
+ self._set_status(0, "[%s]Info: Uninstalled packages: %s"
+ % (self.NAME,
+ ', '.join(item for item in uninstalled)))
+
+ return True
+
+ def check_apt_snmp(self, pkg_module):
+ """do apt health check."""
+ return None
+
+ def check_snmp_mibs(self):
+ """Checks if SNMP MIB files are properly placed."""
+
+ print "Checking SNMP MIBs......",
+ conf_err_msg = health_check_utils.check_path(self.NAME,
+ '/etc/snmp/snmp.conf')
+ if not conf_err_msg == "":
+ self._set_status(0, conf_err_msg)
+
+ mibs_err_msg = health_check_utils.check_path(
+ self.NAME,
+ '/usr/local/share/snmp/mibs')
+ if not mibs_err_msg == "":
+ self._set_status(0, mibs_err_msg)
+
+ return True
diff --git a/compass-tasks-base/actions/health_check/check_misc.py b/compass-tasks-base/actions/health_check/check_misc.py
new file mode 100644
index 0000000..b8beb1b
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_misc.py
@@ -0,0 +1,219 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Miscellaneous Health Check for Compass."""
+import logging
+
+from compass.actions.health_check import base
+from compass.actions.health_check import utils as health_check_utils
+
+
+class MiscCheck(base.BaseCheck):
+ """health check for misc."""
+ NAME = "Miscellaneous Check"
+
+ MISC_MAPPING = {
+ "yum": "rsyslog ntp iproute openssh-clients python git wget "
+ "python-setuptools "
+ "amqp mod_wsgi httpd squid "
+ "dhcp bind rsync yum-utils xinetd tftp-server gcc "
+ "net-snmp-utils net-snmp".split(" "),
+ "pip": "netaddr flask flask_script flask_restful amqplib "
+ "flask_sqlalchemy paramiko mock celery six discover daemon "
+ "unittest2 chef".split(" "),
+ "disable": "iptables ip6tables".split(" "),
+ "enable": "httpd squid xinetd dhcpd named sshd rsyslog cobblerd "
+ "ntpd compass-celeryd compass-progress-updated".split(" "),
+ }
+
+ def run(self):
+ """do health check."""
+ self.check_linux_dependencies()
+ print "[Done]"
+ self.check_pip_dependencies()
+ print "[Done]"
+ self.check_ntp()
+ print "[Done]"
+ self.check_rsyslogd()
+ print "[Done]"
+ self.check_chkconfig()
+ print "[Done]"
+ self.check_selinux()
+ print "[Done]"
+
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: Miscellaneous check has completed "
+ "No problems found, all systems go." % self.NAME)
+ return (self.code, self.messages)
+
+ def check_linux_dependencies(self):
+ """Checks if dependencies are installed."""
+ print "Checking Linux dependencies....",
+ if self.dist in ("centos", "redhat", "fedora", "scientific linux"):
+ pkg_type = "yum"
+ else:
+ pkg_type = "apt"
+
+ try:
+ pkg_module = __import__(pkg_type)
+ except Exception:
+ self._set_status(
+ 0,
+ "[%s]Error: No module named %s, "
+ "please install it first." % (self.NAME, pkg_type))
+ return True
+
+ logging.info('import %s: %s', pkg_type, pkg_module)
+ method_name = 'self.check_' + pkg_type + '_dependencies(pkg_module)'
+ eval(method_name)
+
+ def check_yum_dependencies(self, pkg_module):
+ """Checks if yum dependencies are installed.
+
+ :param pkg_module : python yum library
+ :type pkg_module : python module
+
+ """
+ print "Checking Yum dependencies......",
+ yum_base = pkg_module.YumBase()
+ uninstalled = []
+ for package in self.MISC_MAPPING["yum"]:
+ if len(yum_base.rpmdb.searchNevra(name=package)) == 0:
+ self._set_status(
+ 0,
+ "[%s]Error: %s package is required"
+ % (self.NAME, package))
+ uninstalled.append(package)
+
+ if len(uninstalled) != 0:
+ self._set_status(
+ 0,
+ "[%s]Info: Uninstalled yum packages: %s"
+ % (self.NAME, ', '.join(item for item in uninstalled)))
+
+ return True
+
+ def check_pip_dependencies(self):
+ """Checks if required pip packages are installed."""
+ print "Checking pip dependencies......",
+ uninstalled = []
+ for module in self.MISC_MAPPING['pip']:
+ try:
+ __import__(module)
+ except Exception:
+ self._set_status(
+ 0,
+ "[%s]Error: pip package %s is requred"
+ % (self.NAME, module))
+ uninstalled.append(module)
+
+ if len(uninstalled) != 0:
+ self._set_status(
+ 0,
+ "[%s]Info: Uninstalled pip packages: %s"
+ % (self.NAME, ', '.join(item for item in uninstalled)))
+
+ return True
+
+ def check_ntp(self):
+ """Validates ntp configuration and service."""
+
+ print "Checking NTP......",
+ conf_err_msg = health_check_utils.check_path(self.NAME,
+ '/etc/ntp.conf')
+ if not conf_err_msg == "":
+ self._set_status(0, conf_err_msg)
+
+ serv_err_msg = health_check_utils.check_service_running(self.NAME,
+ 'ntpd')
+ if not serv_err_msg == "":
+ self._set_status(0, serv_err_msg)
+
+ return True
+
+ def check_rsyslogd(self):
+ """Validates rsyslogd configuration and service."""
+
+ print "Checking rsyslog......",
+ conf_err_msg = health_check_utils.check_path(self.NAME,
+ '/etc/rsyslog.conf')
+ if not conf_err_msg == "":
+ self._set_status(0, conf_err_msg)
+
+ dir_err_msg = health_check_utils.check_path(self.NAME,
+ '/etc/rsyslog.d/')
+ if not dir_err_msg == "":
+ self._set_status(0, dir_err_msg)
+
+ serv_err_msg = health_check_utils.check_service_running(self.NAME,
+ 'rsyslogd')
+ if not serv_err_msg == "":
+ self._set_status(0, serv_err_msg)
+
+ return True
+
+ def check_chkconfig(self):
+ """Check if required services are enabled on the start up."""
+
+ print "Checking chkconfig......",
+ serv_to_disable = []
+ for serv in self.MISC_MAPPING["disable"]:
+ if health_check_utils.check_chkconfig(serv) is True:
+ self._set_status(
+ 0,
+ "[%s]Error: %s is not disabled"
+ % (self.NAME, serv))
+ serv_to_disable.append(serv)
+
+ if len(serv_to_disable) != 0:
+ self._set_status(
+ 0,
+ "[%s]Info: You need to disable these services "
+ "on system start-up: %s"
+ % (self.NAME,
+ ", ".join(item for item in serv_to_disable)))
+
+ serv_to_enable = []
+ for serv in self.MISC_MAPPING["enable"]:
+ if health_check_utils.check_chkconfig(serv) is False:
+ self._set_status(
+ 0, "[%s]Error: %s is disabled" % (self.NAME, serv))
+ serv_to_enable.append(serv)
+
+ if len(serv_to_enable) != 0:
+ self._set_status(0, "[%s]Info: You need to enable these "
+ "services on system start-up: %s"
+ % (self.NAME,
+ ", ".join(item for item in serv_to_enable)))
+
+ return True
+
+ def check_selinux(self):
+ """Check if SELinux is disabled."""
+ print "Checking Selinux......",
+ disabled = False
+ with open("/etc/selinux/config") as selinux:
+ for line in selinux:
+ if "SELINUX=disabled" in line:
+ disabled = True
+ break
+
+ if disabled is False:
+ self._set_status(
+ 0,
+ "[%s]Selinux is not disabled, "
+ "please disable it in /etc/selinux/config." % self.NAME)
+
+ return True
diff --git a/compass-tasks-base/actions/health_check/check_os_installer.py b/compass-tasks-base/actions/health_check/check_os_installer.py
new file mode 100644
index 0000000..6ef9818
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_os_installer.py
@@ -0,0 +1,151 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Compass Health Check module for OS Installer."""
+
+import os
+import xmlrpclib
+
+from compass.actions.health_check import base
+
+
+class OsInstallerCheck(base.BaseCheck):
+ """os installer health check."""
+ NAME = "OS Installer Check"
+
+ def run(self):
+ """do health check."""
+ method_name = 'self.' + self.os_installer['name'] + '_check()'
+ return eval(method_name)
+
+ def cobbler_check(self):
+ """Runs cobbler check from xmlrpc client."""
+ try:
+ remote = xmlrpclib.Server(
+ self.os_installer['cobbler_url'],
+ allow_none=True)
+ credentials = self.os_installer['credentials']
+ token = remote.login(
+ credentials['username'], credentials['password'])
+ except Exception:
+ self.code = 0
+ self.messages.append(
+ "[%s]Error: Cannot login to Cobbler with "
+ "the tokens provided in the config file"
+ % self.NAME)
+ self.messages.append(
+ "[%s]Error: Failed to connect to Cobbler "
+ "API, please check if /etc/cobbler/setting "
+ "is properly configured" % self.NAME)
+ return (self.code, self.messages)
+
+ check_result = remote.check(token)
+
+ for index, message in enumerate(check_result):
+ if "SELinux" in message:
+ check_result.pop(index)
+
+ if len(check_result) != 0:
+ self.code = 0
+ for error_msg in check_result:
+ self.messages.append("[%s]Error: " % self.NAME + error_msg)
+
+ if len(remote.get_distros()) == 0:
+ self._set_status(0,
+ "[%s]Error: No Cobbler distros found" % self.NAME)
+
+ if len(remote.get_profiles()) == 0:
+ self._set_status(0,
+ "[%s]Error: No Cobbler profiles found"
+ % self.NAME)
+
+ found_ppa = False
+ if len(remote.get_repos()) != 0:
+ for repo in remote.get_repos():
+ if 'ppa_repo' in repo['mirror']:
+ found_ppa = True
+ break
+
+ if found_ppa is False:
+ self._set_status(0,
+ "[%s]Error: No repository ppa_repo found"
+ % self.NAME)
+
+ path_map = {
+ 'match_kickstart': (
+ '/var/lib/cobbler/kickstarts/',
+ ['default.ks', 'default.seed']
+ ),
+ 'match_snippets': (
+ '/var/lib/cobbler/snippets/',
+ [
+ 'kickstart_done',
+ 'kickstart_start',
+ 'kickstart_pre_partition_disks',
+ 'kickstart_partition_disks',
+ 'kickstart_pre_anamon',
+ 'kickstart_post_anamon',
+ 'kickstart_pre_install_network_config',
+ 'kickstart_network_config',
+ 'kickstart_post_install_network_config',
+ 'kickstart_chef',
+ 'kickstart_ntp',
+ 'kickstart_yum_repo_config',
+ 'preseed_pre_partition_disks',
+ 'preseed_partition_disks',
+ 'preseed_pre_anamon',
+ 'preseed_post_anamon',
+ 'preseed_pre_install_network_config',
+ 'preseed_network_config',
+ 'preseed_post_install_network_config',
+ 'preseed_chef',
+ 'preseed_ntp',
+ 'preseed_apt_repo_config',
+ ]
+ ),
+ 'match_ks_mirror': (
+ '/var/www/cobbler/',
+ ['ks_mirror']
+ ),
+ 'match_repo_mirror': (
+ '/var/www/cobbler/',
+ ['repo_mirror']
+ ),
+ 'match_iso': (
+ '/var/lib/cobbler/',
+ ['iso']
+ ),
+ }
+ not_exists = []
+ for key in path_map.keys():
+ for path in path_map[key][1]:
+ if not os.path.exists(path_map[key][0] + path):
+ not_exists.append(path_map[key][0] + path)
+
+ if len(not_exists) != 0:
+ self._set_status(
+ 0,
+ "[%s]Error: These locations do not exist: "
+ "%s" % (
+ self.NAME,
+ ', '.join(item for item in not_exists)
+ )
+ )
+
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: OS Installer health check has completed."
+ " No problems found, all systems go." % self.NAME)
+
+ return (self.code, self.messages)
diff --git a/compass-tasks-base/actions/health_check/check_package_installer.py b/compass-tasks-base/actions/health_check/check_package_installer.py
new file mode 100644
index 0000000..efcd8e8
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_package_installer.py
@@ -0,0 +1,68 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for Package Installer."""
+import logging
+import os
+import requests
+
+from compass.actions.health_check import base
+from compass.actions.health_check import utils as health_check_utils
+
+
+class PackageInstallerCheck(base.BaseCheck):
+ """package installer health check class."""
+ NAME = "Package Installer Check"
+
+ def run(self):
+ """do health check."""
+ method_name = "self." + self.package_installer['name'] + "_check()"
+ return eval(method_name)
+
+ def chef_check(self):
+ """Checks chef setting, cookbooks and roles."""
+ self.check_chef_config_dir()
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: Package installer health check "
+ "has completed. No problems found, all systems "
+ "go." % self.NAME)
+
+ return (self.code, self.messages)
+
+ def check_chef_config_dir(self):
+ """Validates chef configuration directories."""
+
+ print "Checking Chef configurations......",
+ message = health_check_utils.check_path(self.NAME, '/etc/chef-server/')
+ if not message == "":
+ self._set_status(0, message)
+
+ message = health_check_utils.check_path(self.NAME, '/opt/chef-server/')
+ if not message == "":
+ self._set_status(0, message)
+
+ return None
+
+ def ansible_check(self):
+ """Placeholder for ansible check."""
+ print "Checking ansible......"
+ print ("[Done]")
+ self.code == 1
+ self.messages.append(
+ "[%s]Info: Package installer health check "
+ "has completed. No problems found, all systems "
+ "go." % self.NAME)
+ return (self.code, self.messages)
diff --git a/compass-tasks-base/actions/health_check/check_squid.py b/compass-tasks-base/actions/health_check/check_squid.py
new file mode 100644
index 0000000..5628a63
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_squid.py
@@ -0,0 +1,128 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for Squid service."""
+import commands
+import os
+import pwd
+import socket
+
+from compass.actions.health_check import base
+from compass.actions.health_check import utils as health_check_utils
+
+
+class SquidCheck(base.BaseCheck):
+ """Squid health check class."""
+ NAME = "Squid Check"
+
+ def run(self):
+ """do health check."""
+ self.check_squid_files()
+ print "[Done]"
+ self.check_squid_service()
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: Squid health check has completed. "
+ "No problems found, all systems go." % self.NAME)
+ return (self.code, self.messages)
+
+ def check_squid_files(self):
+ """Validates squid config, cache directory and ownership."""
+ print "Checking Squid Files......",
+ var_map = {
+ 'match_squid_conf': False,
+ 'match_squid_cache': False,
+ 'match_squid_ownership': False,
+ }
+
+ conf_err_msg = health_check_utils.check_path(
+ self.NAME,
+ "/etc/squid/squid.conf")
+ if not conf_err_msg == "":
+ self._set_status(0, conf_err_msg)
+ elif int(oct(os.stat('/etc/squid/squid.conf').st_mode)) < 100644:
+ self._set_status(
+ 0,
+ "[%s]Error: squid.conf has incorrect "
+ "file permissions" % self.NAME)
+ else:
+ var_map['match_squid_conf'] = True
+
+ squid_path_err_msg = health_check_utils.check_path(
+ self.NAME, '/var/squid/')
+ if not squid_path_err_msg == "":
+ self._set_status(0, squid_path_err_msg)
+ elif health_check_utils.check_path(
+ self.NAME,
+ '/var/squid/cache'
+ ) != "":
+ self._set_status(
+ 0,
+ health_check_utils.check_path(
+ self.NAME,
+ '/var/squid/cache'
+ )
+ )
+ else:
+ var_map['match_squid_cache'] = True
+ uid = os.stat('/var/squid/').st_uid
+ gid = os.stat('/var/squid/').st_gid
+ if uid != gid or pwd.getpwuid(23).pw_name != 'squid':
+ self._set_status(
+ 0,
+ "[%s]Error: /var/squid directory ownership "
+ "misconfigured" % self.NAME)
+ else:
+ var_map['match_squid_ownership'] = True
+
+ fails = []
+ for key in var_map.keys():
+ if var_map[key] is False:
+ fails.append(key)
+
+ if len(fails) != 0:
+ self.messages.append(
+ "[%s]Info: Failed components for squid config: "
+ "%s" % (
+ self.NAME,
+ ', '.join(item for item in fails)
+ )
+ )
+ return True
+
+ def check_squid_service(self):
+ """Checks if squid is running on port 3128."""
+
+ print "Checking Squid service......",
+ if 'squid' not in commands.getoutput('ps -ef'):
+ self._set_status(
+ 0,
+ "[%s]Error: squid service does not seem "
+ "running" % self.NAME)
+
+ try:
+ if 'squid' != socket.getservbyport(3128):
+ self._set_status(
+ 0,
+ "[%s]Error: squid is not listening on "
+ "3128" % self.NAME)
+
+ except Exception:
+ self._set_status(
+ 0,
+ "[%s]Error: No service is listening on 3128, "
+ "squid failed" % self.NAME)
+
+ return True
diff --git a/compass-tasks-base/actions/health_check/check_tftp.py b/compass-tasks-base/actions/health_check/check_tftp.py
new file mode 100644
index 0000000..7ca6405
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/check_tftp.py
@@ -0,0 +1,96 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Health Check module for TFTP service."""
+import os
+import socket
+import xmlrpclib
+
+from compass.actions.health_check import base
+from compass.actions.health_check import utils as health_check_utils
+
+
+class TftpCheck(base.BaseCheck):
+ """tftp health check class."""
+ NAME = "TFTP Check"
+
+ def run(self):
+ """do health check."""
+ method_name = "self.check_" + self.os_installer['name'] + "_tftp()"
+ return eval(method_name)
+
+ def check_cobbler_tftp(self):
+ """Checks if Cobbler manages TFTP service.
+
+ :note: we assume TFTP service is running at the
+ same machine where this health check runs at
+ """
+
+ try:
+ remote = xmlrpclib.Server(
+ self.os_installer['cobbler_url'],
+ allow_none=True)
+ credentials = self.os_installer['credentials']
+ remote.login(
+ credentials['username'], credentials['password'])
+ except Exception:
+ self._set_status(
+ 0,
+ "[%s]Error: Cannot login to Cobbler with the tokens "
+ " provided in the config file" % self.NAME)
+ return (self.code, self.messages)
+
+ cobbler_settings = remote.get_settings()
+ if cobbler_settings['manage_tftp'] == 0:
+ self.messages.append(
+ '[TFTP]Info: tftp service is not managed by Compass')
+ return (0, self.messages)
+ self.check_tftp_dir()
+ print "[Done]"
+ self.check_tftp_service()
+ print "[Done]"
+ if self.code == 1:
+ self.messages.append(
+ "[%s]Info: tftp service health check has completed. "
+ "No problems found, all systems go." % self.NAME)
+
+ return (self.code, self.messages)
+
+ def check_tftp_dir(self):
+ """Validates TFTP directories and configurations."""
+ print "Checking TFTP directories......",
+ if not os.path.exists('/var/lib/tftpboot/'):
+ self._set_status(
+ 0,
+ "[%s]Error: No tftp-boot libraries found, "
+ "please check if tftp server is properly "
+ "installed/managed" % self.NAME)
+
+ return True
+
+ def check_tftp_service(self):
+ """Checks if TFTP is running on port 69."""
+ print "Checking TFTP services......",
+ serv_err_msg = health_check_utils.check_service_running(self.NAME,
+ 'xinetd')
+ if not serv_err_msg == "":
+ self._set_status(0, serv_err_msg)
+
+ if 'tftp' != socket.getservbyport(69):
+ self._set_status(
+ 0,
+ "[%s]Error: tftp doesn't seem to be listening "
+ "on Port 60." % self.NAME)
+
+ return True
diff --git a/compass-tasks-base/actions/health_check/utils.py b/compass-tasks-base/actions/health_check/utils.py
new file mode 100644
index 0000000..369c5b6
--- /dev/null
+++ b/compass-tasks-base/actions/health_check/utils.py
@@ -0,0 +1,114 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Compass Health Check heavy-lifting utilities"""
+import commands
+import os
+import platform
+import re
+
+
+def validate_setting(module, setting, param):
+ """Checks if a Compass setting exists in the config file.
+
+ :param module : module name to be checked
+ :type module : string
+ :param setting : compass setting wrapper
+ :type setting : python module
+ :param param : settings defined in compass config file
+ :type param : string
+
+ """
+ if hasattr(setting, param):
+ return True
+ else:
+ err_msg = "[%s]Error: no %s defined" % (module, param)
+ return err_msg
+
+
+def get_dist():
+ """Returns the operating system related information."""
+
+ os_version, version, release = platform.linux_distribution()
+ return (os_version.lower().strip(), version, release.lower().strip())
+
+
+def check_path(module_name, path):
+ """Checks if a directory or file exisits.
+
+ :param module_name : module name to be checked
+ :type module_name : string
+ :param path : path of the directory of file
+ :type path : string
+
+ """
+ err_msg = ""
+ if not os.path.exists(path):
+ err_msg = (
+ "[%s]Error: %s does not exist, "
+ "please check your configurations.") % (module_name, path)
+ return err_msg
+
+
+def check_service_running(module_name, service_name):
+ """Checks if a certain service is running.
+
+ :param module_name : module name to be checked
+ :type module_name : string
+ :param service_name : service name to be checked
+ :type service_name : string
+
+ """
+ err_msg = ""
+ if service_name not in commands.getoutput('ps -ef'):
+ err_msg = "[%s]Error: %s is not running." % (
+ module_name, service_name)
+
+ return err_msg
+
+
+def check_chkconfig(service_name):
+ """Checks if a service is enabled at the start up.
+
+ :param service_name : service name to be checked
+ :type service_name : string
+
+ """
+ chk_on = False
+ for service in os.listdir('/etc/rc3.d/'):
+ if service_name in service and 'S' in service:
+ chk_on = True
+ break
+
+ return chk_on
+
+
+def strip_name(name):
+ """Reformats names."""
+ if not any([s in name for s in "(,),-,_".split(',')]):
+ return name
+
+ paren_regex = re.compile("(.*?)\s*\(")
+ dash_regex = re.compile("(.*?)\s*\-")
+ under_dash_regex = re.compile("(.*?)\s*\_")
+
+ r1 = paren_regex.match(name)
+ r2 = dash_regex.match(name)
+ r3 = under_dash_regex.match(name)
+ shortest = 'AVeryLongStringForDefualt'
+ for r in [r1, r2, r3]:
+ if r and len(r.group(1)) < len(shortest):
+ shortest = r.group(1)
+
+ return shortest
diff --git a/compass-tasks-base/actions/install_callback.py b/compass-tasks-base/actions/install_callback.py
new file mode 100644
index 0000000..14d2639
--- /dev/null
+++ b/compass-tasks-base/actions/install_callback.py
@@ -0,0 +1,181 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to receive installation callback.
+
+ .. moduleauthor:: Xiaodong Wang <xiaodongwang@huawei.com>
+"""
+import logging
+
+from compass.actions import util
+from compass.db.api import cluster as cluster_api
+from compass.db.api import host as host_api
+from compass.db.api import user as user_db
+from compass.deployment.deploy_manager import DeployManager
+from compass.deployment.utils import constants as const
+
+
+def os_installed(
+ host_id, clusterhosts_ready, clusters_os_ready,
+ username=None
+):
+ """Callback when os is installed.
+
+ :param host_id: host that os is installed.
+ :type host_id: integer
+ :param clusterhosts_ready: the clusterhosts that should trigger ready.
+ :param clusters_os_ready: the cluster that should trigger os ready.
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action') as lock:
+ if not lock:
+ raise Exception(
+ 'failed to acquire lock to '
+ 'do the post action after os installation'
+ )
+ logging.info(
+ 'os installed on host %s '
+ 'with cluster host ready %s cluster os ready %s',
+ host_id, clusterhosts_ready, clusters_os_ready
+ )
+ if username:
+ user = user_db.get_user_object(username)
+ else:
+ user = None
+ os_installed_triggered = False
+ for cluster_id, clusterhost_ready in clusterhosts_ready.items():
+ if not clusterhost_ready and os_installed_triggered:
+ continue
+ cluster_id = int(cluster_id)
+ cluster_info = util.ActionHelper.get_cluster_info(
+ cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, [host_id], user)
+
+ deploy_manager = DeployManager(
+ adapter_info, cluster_info, hosts_info)
+
+ if not os_installed_triggered:
+ deploy_manager.os_installed()
+ util.ActionHelper.host_ready(host_id, True, user)
+ os_installed_triggered = True
+
+ if clusterhost_ready:
+ # deploy_manager.cluster_os_installed()
+ util.ActionHelper.cluster_host_ready(
+ cluster_id, host_id, False, user
+ )
+
+ if util.ActionHelper.is_cluster_os_ready(cluster_id, user):
+ logging.info("deploy_manager begin cluster_os_installed")
+ deploy_manager.cluster_os_installed()
+
+
+def package_installed(
+ cluster_id, host_id, cluster_ready,
+ host_ready, username=None
+):
+ """Callback when package is installed.
+
+ :param cluster_id: cluster id.
+ :param host_id: host id.
+ :param cluster_ready: if the cluster should trigger ready.
+ :param host_ready: if the host should trigger ready.
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action') as lock:
+ if not lock:
+ raise Exception(
+ 'failed to acquire lock to '
+ 'do the post action after package installation'
+ )
+ logging.info(
+ 'package installed on cluster %s host %s '
+ 'with cluster ready %s host ready %s',
+ cluster_id, host_id, cluster_ready, host_ready
+ )
+
+ if username:
+ user = user_db.get_user_object(username)
+ else:
+ user = None
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, [host_id], user)
+
+ deploy_manager = DeployManager(adapter_info, cluster_info, hosts_info)
+
+ deploy_manager.package_installed()
+ util.ActionHelper.cluster_host_ready(cluster_id, host_id, True, user)
+ if cluster_ready:
+ util.ActionHelper.cluster_ready(cluster_id, False, user)
+ if host_ready:
+ util.ActionHelper.host_ready(host_id, False, user)
+
+
+def cluster_installed(
+ cluster_id, clusterhosts_ready,
+ username=None
+):
+ """Callback when cluster is installed.
+
+ :param cluster_id: cluster id
+ :param clusterhosts_ready: clusterhosts that should trigger ready.
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action') as lock:
+ if not lock:
+ raise Exception(
+ 'failed to acquire lock to '
+ 'do the post action after cluster installation'
+ )
+ logging.info(
+ 'package installed on cluster %s with clusterhosts ready %s',
+ cluster_id, clusterhosts_ready
+ )
+ if username:
+ user = user_db.get_user_object(username)
+ else:
+ user = None
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, clusterhosts_ready.keys(), user)
+
+ deploy_manager = DeployManager(adapter_info, cluster_info, hosts_info)
+
+ deploy_manager.cluster_installed()
+ util.ActionHelper.cluster_ready(cluster_id, True, user)
+ for host_id, clusterhost_ready in clusterhosts_ready.items():
+ if clusterhost_ready:
+ util.ActionHelper.cluster_host_ready(
+ cluster_id, host_id, False, user
+ )
diff --git a/compass-tasks-base/actions/patch.py b/compass-tasks-base/actions/patch.py
new file mode 100644
index 0000000..6d29be6
--- /dev/null
+++ b/compass-tasks-base/actions/patch.py
@@ -0,0 +1,69 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to patch an existing cluster
+"""
+import logging
+import simplejson as json
+
+from compass.actions import util
+from compass.db.api import cluster as cluster_db
+from compass.db.api import user as user_db
+from compass.deployment.deploy_manager import Patcher
+from compass.deployment.utils import constants as const
+
+
+def patch(cluster_id, username=None):
+ """Patch cluster.
+
+ :param cluster_id: id of the cluster
+ :type cluster_id: int
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action', timeout=1000) as lock:
+ if not lock:
+ raise Exception('failed to acquire lock to deploy')
+
+ user = user_db.get_user_object(username)
+ cluster_hosts = cluster_db.list_cluster_hosts(cluster_id, user)
+ hosts_id_list = [host['id'] for host in cluster_hosts]
+ cluster_info = util.ActionHelper.get_cluster_info(cluster_id, user)
+ adapter_id = cluster_info[const.ADAPTER_ID]
+
+ adapter_info = util.ActionHelper.get_adapter_info(
+ adapter_id, cluster_id, user)
+ hosts_info = util.ActionHelper.get_hosts_info(
+ cluster_id, hosts_id_list, user)
+ patch_successful = True
+ try:
+ patcher = Patcher(
+ adapter_info, cluster_info, hosts_info, cluster_hosts)
+ patched_config = patcher.patch()
+ except Exception as error:
+ logging.exception(error)
+ patch_successful = False
+
+ if patch_successful:
+ clean_payload = '{"patched_roles": []}'
+ clean_payload = json.loads(clean_payload)
+ for cluster_host in cluster_hosts:
+ cluster_db.update_cluster_host(
+ cluster_id, cluster_host['id'], user, **clean_payload)
+ logging.info(
+ "cleaning up patched roles for host id: %s",
+ cluster_host['id']
+ )
+ logging.info("Patch successful: %s", patched_config)
diff --git a/compass-tasks-base/actions/poll_switch.py b/compass-tasks-base/actions/poll_switch.py
new file mode 100644
index 0000000..5c29b01
--- /dev/null
+++ b/compass-tasks-base/actions/poll_switch.py
@@ -0,0 +1,162 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to provider function to poll switch."""
+import logging
+import netaddr
+
+from compass.actions import util
+from compass.db.api import database
+from compass.db.api import switch as switch_api
+from compass.db.api import user as user_api
+from compass.hdsdiscovery.hdmanager import HDManager
+
+
+def _poll_switch(ip_addr, credentials, req_obj='mac', oper="SCAN"):
+ """Poll switch by ip addr.
+
+
+ Args:
+ ip_addr: ip addr of the switch.
+ credentials: credentials of the switch.
+
+ Returns: switch attributes dict and list of machine attributes dict.
+ """
+ under_monitoring = 'under_monitoring'
+ unreachable = 'unreachable'
+ polling_error = 'error'
+ hdmanager = HDManager()
+ vendor, state, err_msg = hdmanager.get_vendor(ip_addr, credentials)
+ if not vendor:
+ logging.info("*****error_msg: %s****", err_msg)
+ logging.error('no vendor found or match switch %s', ip_addr)
+ return (
+ {
+ 'vendor': vendor, 'state': state, 'err_msg': err_msg
+ }, {
+ }
+ )
+
+ logging.debug(
+ 'hdmanager learn switch from %s', ip_addr
+ )
+ results = []
+ try:
+ results = hdmanager.learn(
+ ip_addr, credentials, vendor, req_obj, oper
+ )
+ except Exception as error:
+ logging.exception(error)
+ state = unreachable
+ err_msg = (
+ 'SNMP walk for querying MAC addresses timedout'
+ )
+ return (
+ {
+ 'vendor': vendor, 'state': state, 'err_msg': err_msg
+ }, {
+ }
+ )
+
+ logging.info("pollswitch %s result: %s", ip_addr, results)
+ if not results:
+ logging.error(
+ 'no result learned from %s', ip_addr
+ )
+ state = polling_error
+ err_msg = 'No result learned from SNMP walk'
+ return (
+ {'vendor': vendor, 'state': state, 'err_msg': err_msg},
+ {}
+ )
+
+ logging.info('poll switch result: %s' % str(results))
+ machine_dicts = {}
+ for machine in results:
+ mac = machine['mac']
+ port = machine['port']
+ vlan = int(machine['vlan'])
+ if vlan:
+ vlans = [vlan]
+ else:
+ vlans = []
+ if mac not in machine_dicts:
+ machine_dicts[mac] = {'mac': mac, 'port': port, 'vlans': vlans}
+ else:
+ machine_dicts[mac]['port'] = port
+ machine_dicts[mac]['vlans'].extend(vlans)
+
+ logging.debug('update switch %s state to under monitoring', ip_addr)
+ state = under_monitoring
+ return (
+ {'vendor': vendor, 'state': state, 'err_msg': err_msg},
+ machine_dicts.values()
+ )
+
+
+def poll_switch(poller_email, ip_addr, credentials,
+ req_obj='mac', oper="SCAN"):
+ """Query switch and update switch machines.
+
+ .. note::
+ When polling switch succeeds, for each mac it got from polling switch,
+ A Machine record associated with the switch is added to the database.
+
+ :param ip_addr: switch ip address.
+ :type ip_addr: str
+ :param credentials: switch crednetials.
+ :type credentials: dict
+ :param req_obj: the object requested to query from switch.
+ :type req_obj: str
+ :param oper: the operation to query the switch.
+ :type oper: str, should be one of ['SCAN', 'GET', 'SET']
+
+ .. note::
+ The function should be called out of database session scope.
+ """
+ poller = user_api.get_user_object(poller_email)
+ ip_int = long(netaddr.IPAddress(ip_addr))
+ with util.lock('poll switch %s' % ip_addr, timeout=120) as lock:
+ if not lock:
+ raise Exception(
+ 'failed to acquire lock to poll switch %s' % ip_addr
+ )
+
+ # TODO(grace): before repoll the switch, set the state to repolling.
+ # and when the poll switch is timeout, set the state to error.
+ # the frontend should only consider some main state like INTIALIZED,
+ # ERROR and SUCCESSFUL, REPOLLING is as an intermediate state to
+ # indicate the switch is in learning the mac of the machines connected
+ # to it.
+ logging.debug('poll switch: %s', ip_addr)
+ switch_dict, machine_dicts = _poll_switch(
+ ip_addr, credentials, req_obj=req_obj, oper=oper
+ )
+ switches = switch_api.list_switches(ip_int=ip_int, user=poller)
+ if not switches:
+ logging.error('no switch found for %s', ip_addr)
+ return
+
+ for switch in switches:
+ for machine_dict in machine_dicts:
+ logging.info('add machine: %s', machine_dict)
+ machine_dict['owner_id'] = poller.id
+ switch_api.add_switch_machine(
+ switch['id'], False, user=poller, **machine_dict
+ )
+ switch_api.update_switch(
+ switch['id'],
+ user=poller,
+ **switch_dict
+ )
diff --git a/compass-tasks-base/actions/reinstall.py b/compass-tasks-base/actions/reinstall.py
new file mode 100644
index 0000000..62d1bcb
--- /dev/null
+++ b/compass-tasks-base/actions/reinstall.py
@@ -0,0 +1,38 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to reinstall a given cluster
+
+ .. moduleauthor:: Xiaodong Wang <xiaodongwang@huawei.com>
+"""
+import logging
+
+from compass.actions import util
+from compass.db.api import database
+
+
+def reinstall(cluster_hosts):
+ """Reinstall clusters.
+
+ :param cluster_hosts: clusters and hosts in each cluster to reinstall.
+ :type cluster_hosts: dict of int or str to list of int or str
+
+ .. note::
+ The function should be called out of database session.
+ """
+ with util.lock('serialized_action') as lock:
+ if not lock:
+ raise Exception(
+ 'failed to acquire lock to reinstall')
+ logging.debug('reinstall cluster_hosts: %s', cluster_hosts)
diff --git a/compass-tasks-base/actions/search.py b/compass-tasks-base/actions/search.py
new file mode 100644
index 0000000..73ce1d9
--- /dev/null
+++ b/compass-tasks-base/actions/search.py
@@ -0,0 +1,46 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to search configs of given clusters
+
+ .. moduleauthor:: Xiaodong Wang <xiaodongwang@huawei.com>
+"""
+import logging
+
+from compass.actions import util
+from compass.config_management.utils.config_manager import ConfigManager
+from compass.db.api import database
+
+
+def search(cluster_hosts, cluster_propreties_match,
+ cluster_properties_name, host_properties_match,
+ host_properties_name):
+ """search clusters.
+
+ :param cluster_hosts: clusters and hosts in each cluster to search.
+ :type cluster_hosts: dict of int or str to list of int or str
+
+ .. note::
+ The function should be called out of database session.
+ """
+ logging.debug('search cluster_hosts: %s', cluster_hosts)
+ with database.session():
+ cluster_hosts, os_versions, target_systems = (
+ util.update_cluster_hosts(cluster_hosts))
+ manager = ConfigManager()
+ return manager.filter_cluster_and_hosts(
+ cluster_hosts, os_versions,
+ target_systems, cluster_propreties_match,
+ cluster_properties_name, host_properties_match,
+ host_properties_name)
diff --git a/compass-tasks-base/actions/update_progress.py b/compass-tasks-base/actions/update_progress.py
new file mode 100644
index 0000000..67a9963
--- /dev/null
+++ b/compass-tasks-base/actions/update_progress.py
@@ -0,0 +1,298 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to update status and installing progress of the given cluster.
+
+ .. moduleauthor:: Xiaodong Wang <xiaodongwang@huawei.com>
+"""
+import logging
+
+from compass.actions import util
+from compass.db.api import adapter_holder as adapter_api
+from compass.db.api import cluster as cluster_api
+from compass.db.api import host as host_api
+from compass.db.api import user as user_api
+from compass.log_analyzor import progress_calculator
+from compass.utils import setting_wrapper as setting
+
+
+def update_progress():
+ """Update status and installing progress of the given cluster.
+
+ :param cluster_hosts: clusters and hosts in each cluster to update.
+ :type cluster_hosts: dict of int or str to list of int or str
+
+ .. note::
+ The function should be called out of the database session scope.
+ In the function, it will update the database cluster_state and
+ host_state table for the deploying cluster and hosts.
+
+ The function will also query log_progressing_history table to get
+ the lastest installing progress and the position of log it has
+ processed in the last run. The function uses these information to
+ avoid recalculate the progress from the beginning of the log file.
+ After the progress got updated, these information will be stored back
+ to the log_progressing_history for next time run.
+ """
+ with util.lock('log_progressing', timeout=60, blocking=False) as lock:
+ if not lock:
+ logging.error(
+ 'failed to acquire lock to calculate installation progress'
+ )
+ return
+
+ logging.info('update installing progress')
+
+ user = user_api.get_user_object(setting.COMPASS_ADMIN_EMAIL)
+ hosts = host_api.list_hosts(user=user)
+ host_mapping = {}
+ for host in hosts:
+ if 'id' not in host:
+ logging.error('id is not in host %s', host)
+ continue
+ host_id = host['id']
+ if 'os_name' not in host:
+ logging.error('os_name is not in host %s', host)
+ continue
+ if 'os_installer' not in host:
+ logging.error('os_installer is not in host %s', host)
+ continue
+ host_dirname = setting.HOST_INSTALLATION_LOGDIR_NAME
+ if host_dirname not in host:
+ logging.error(
+ '%s is not in host %s', host_dirname, host
+ )
+ continue
+ host_state = host_api.get_host_state(host_id, user=user)
+ if 'state' not in host_state:
+ logging.error('state is not in host state %s', host_state)
+ continue
+ if host_state['state'] == 'INSTALLING':
+ host_log_histories = host_api.get_host_log_histories(
+ host_id, user=user
+ )
+ host_log_history_mapping = {}
+ for host_log_history in host_log_histories:
+ if 'filename' not in host_log_history:
+ logging.error(
+ 'filename is not in host log history %s',
+ host_log_history
+ )
+ continue
+ host_log_history_mapping[
+ host_log_history['filename']
+ ] = host_log_history
+ host_mapping[host_id] = (
+ host, host_state, host_log_history_mapping
+ )
+ else:
+ logging.info(
+ 'ignore host state %s since it is not in installing',
+ host_state
+ )
+ adapters = adapter_api.list_adapters(user=user)
+ adapter_mapping = {}
+ for adapter in adapters:
+ if 'id' not in adapter:
+ logging.error(
+ 'id not in adapter %s', adapter
+ )
+ continue
+ if 'package_installer' not in adapter:
+ logging.info(
+ 'package_installer not in adapter %s', adapter
+ )
+ continue
+ adapter_id = adapter['id']
+ adapter_mapping[adapter_id] = adapter
+ clusters = cluster_api.list_clusters(user=user)
+ cluster_mapping = {}
+ for cluster in clusters:
+ if 'id' not in cluster:
+ logging.error('id not in cluster %s', cluster)
+ continue
+ cluster_id = cluster['id']
+ if 'adapter_id' not in cluster:
+ logging.error(
+ 'adapter_id not in cluster %s',
+ cluster
+ )
+ continue
+ cluster_state = cluster_api.get_cluster_state(
+ cluster_id,
+ user=user
+ )
+ if 'state' not in cluster_state:
+ logging.error('state not in cluster state %s', cluster_state)
+ continue
+ cluster_mapping[cluster_id] = (cluster, cluster_state)
+ clusterhosts = cluster_api.list_clusterhosts(user=user)
+ clusterhost_mapping = {}
+ for clusterhost in clusterhosts:
+ if 'clusterhost_id' not in clusterhost:
+ logging.error(
+ 'clusterhost_id not in clusterhost %s',
+ clusterhost
+ )
+ continue
+ clusterhost_id = clusterhost['clusterhost_id']
+ if 'cluster_id' not in clusterhost:
+ logging.error(
+ 'cluster_id not in clusterhost %s',
+ clusterhost
+ )
+ continue
+ cluster_id = clusterhost['cluster_id']
+ if cluster_id not in cluster_mapping:
+ logging.info(
+ 'ignore clusterhost %s '
+ 'since the cluster_id '
+ 'is not in cluster_mapping %s',
+ clusterhost, cluster_mapping
+ )
+ continue
+ cluster, _ = cluster_mapping[cluster_id]
+ if 'flavor_name' not in cluster:
+ logging.error(
+ 'flavor_name is not in clusterhost %s related cluster',
+ clusterhost
+ )
+ continue
+ clusterhost_dirname = setting.CLUSTERHOST_INATALLATION_LOGDIR_NAME
+ if clusterhost_dirname not in clusterhost:
+ logging.error(
+ '%s is not in clusterhost %s',
+ clusterhost_dirname, clusterhost
+ )
+ continue
+ adapter_id = cluster['adapter_id']
+ if adapter_id not in adapter_mapping:
+ logging.info(
+ 'ignore clusterhost %s '
+ 'since the adapter_id %s '
+ 'is not in adaper_mapping %s',
+ clusterhost, adapter_id, adapter_mapping
+ )
+ continue
+ adapter = adapter_mapping[adapter_id]
+ if 'package_installer' not in adapter:
+ logging.info(
+ 'ignore clusterhost %s '
+ 'since the package_installer is not define '
+ 'in adapter %s',
+ clusterhost, adapter
+ )
+ continue
+ package_installer = adapter['package_installer']
+ clusterhost['package_installer'] = package_installer
+ clusterhost['adapter_name'] = adapter['name']
+ clusterhost_state = cluster_api.get_clusterhost_self_state(
+ clusterhost_id, user=user
+ )
+ if 'state' not in clusterhost_state:
+ logging.error(
+ 'state not in clusterhost_state %s',
+ clusterhost_state
+ )
+ continue
+ if clusterhost_state['state'] == 'INSTALLING':
+ clusterhost_log_histories = (
+ cluster_api.get_clusterhost_log_histories(
+ clusterhost_id, user=user
+ )
+ )
+ clusterhost_log_history_mapping = {}
+ for clusterhost_log_history in clusterhost_log_histories:
+ if 'filename' not in clusterhost_log_history:
+ logging.error(
+ 'filename not in clusterhost_log_history %s',
+ clusterhost_log_history
+ )
+ continue
+ clusterhost_log_history_mapping[
+ clusterhost_log_history['filename']
+ ] = clusterhost_log_history
+ clusterhost_mapping[clusterhost_id] = (
+ clusterhost, clusterhost_state,
+ clusterhost_log_history_mapping
+ )
+ else:
+ logging.info(
+ 'ignore clusterhost state %s '
+ 'since it is not in installing',
+ clusterhost_state
+ )
+
+ progress_calculator.update_host_progress(
+ host_mapping)
+ for host_id, (host, host_state, host_log_history_mapping) in (
+ host_mapping.items()
+ ):
+ host_api.update_host_state(
+ host_id, user=user,
+ percentage=host_state.get('percentage', 0),
+ message=host_state.get('message', ''),
+ severity=host_state.get('severity', 'INFO')
+ )
+ for filename, host_log_history in (
+ host_log_history_mapping.items()
+ ):
+ host_api.add_host_log_history(
+ host_id, filename=filename, user=user,
+ position=host_log_history.get('position', 0),
+ percentage=host_log_history.get('percentage', 0),
+ partial_line=host_log_history.get('partial_line', ''),
+ message=host_log_history.get('message', ''),
+ severity=host_log_history.get('severity', 'INFO'),
+ line_matcher_name=host_log_history.get(
+ 'line_matcher_name', 'start'
+ )
+ )
+ progress_calculator.update_clusterhost_progress(
+ clusterhost_mapping)
+ for (
+ clusterhost_id,
+ (clusterhost, clusterhost_state, clusterhost_log_history_mapping)
+ ) in (
+ clusterhost_mapping.items()
+ ):
+ cluster_api.update_clusterhost_state(
+ clusterhost_id, user=user,
+ percentage=clusterhost_state.get('percentage', 0),
+ message=clusterhost_state.get('message', ''),
+ severity=clusterhost_state.get('severity', 'INFO')
+ )
+ for filename, clusterhost_log_history in (
+ clusterhost_log_history_mapping.items()
+ ):
+ cluster_api.add_clusterhost_log_history(
+ clusterhost_id, user=user, filename=filename,
+ position=clusterhost_log_history.get('position', 0),
+ percentage=clusterhost_log_history.get('percentage', 0),
+ partial_line=clusterhost_log_history.get(
+ 'partial_line', ''),
+ message=clusterhost_log_history.get('message', ''),
+ severity=clusterhost_log_history.get('severity', 'INFO'),
+ line_matcher_name=(
+ clusterhost_log_history.get(
+ 'line_matcher_name', 'start'
+ )
+ )
+ )
+ progress_calculator.update_cluster_progress(
+ cluster_mapping)
+ for cluster_id, (cluster, cluster_state) in cluster_mapping.items():
+ cluster_api.update_cluster_state(
+ cluster_id, user=user
+ )
diff --git a/compass-tasks-base/actions/util.py b/compass-tasks-base/actions/util.py
new file mode 100644
index 0000000..4d9f855
--- /dev/null
+++ b/compass-tasks-base/actions/util.py
@@ -0,0 +1,342 @@
+# Copyright 2014 Huawei Technologies Co. Ltd
+#
+# 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.
+
+"""Module to provide util for actions
+
+ .. moduleauthor:: Xiaodong Wang ,xiaodongwang@huawei.com>
+"""
+import logging
+import redis
+
+from contextlib import contextmanager
+
+from compass.db.api import adapter_holder as adapter_db
+from compass.db.api import cluster as cluster_db
+from compass.db.api import host as host_db
+from compass.db.api import machine as machine_db
+from compass.deployment.utils import constants as const
+
+
+@contextmanager
+def lock(lock_name, blocking=True, timeout=10):
+ """acquire a lock to do some actions.
+
+ The lock is acquired by lock_name among the whole distributed
+ systems.
+ """
+ # TODO(xicheng): in future we should explicitly told which redis
+ # server we want to talk to make the lock works on distributed
+ # systems.
+ redis_instance = redis.Redis()
+ instance_lock = redis_instance.lock(lock_name, timeout=timeout)
+ owned = False
+ try:
+ locked = instance_lock.acquire(blocking=blocking)
+ if locked:
+ owned = True
+ logging.debug('acquired lock %s', lock_name)
+ yield instance_lock
+ else:
+ logging.info('lock %s is already hold', lock_name)
+ yield None
+
+ except Exception as error:
+ logging.info(
+ 'redis fails to acquire the lock %s', lock_name)
+ logging.exception(error)
+ yield None
+
+ finally:
+ if owned:
+ instance_lock.acquired_until = 0
+ instance_lock.release()
+ logging.debug('released lock %s', lock_name)
+ else:
+ logging.debug('nothing to release %s', lock_name)
+
+
+class ActionHelper(object):
+
+ @staticmethod
+ def get_adapter_info(adapter_id, cluster_id, user):
+ """Get adapter information. Return a dictionary as below,
+
+ {
+ "id": 1,
+ "name": "xxx",
+ "flavors": [
+ {
+ "flavor_name": "xxx",
+ "roles": ['xxx', 'yyy', ...],
+ "template": "xxx.tmpl"
+ },
+ ...
+ ],
+ "metadata": {
+ "os_config": {
+ ...
+ },
+ "package_config": {
+ ...
+ }
+ },
+ "os_installer": {
+ "name": "cobbler",
+ "settings": {....}
+ },
+ "pk_installer": {
+ "name": "chef",
+ "settings": {....}
+ },
+ ...
+ }
+ To view a complete output, please refer to backend doc.
+ """
+
+ adapter_info = adapter_db.get_adapter(adapter_id, user=user)
+ metadata = cluster_db.get_cluster_metadata(cluster_id, user=user)
+ adapter_info.update({const.METADATA: metadata})
+
+ for flavor_info in adapter_info[const.FLAVORS]:
+ roles = flavor_info[const.ROLES]
+ flavor_info[const.ROLES] = ActionHelper._get_role_names(roles)
+
+ return adapter_info
+
+ @staticmethod
+ def _get_role_names(roles):
+ return [role[const.NAME] for role in roles]
+
+ @staticmethod
+ def get_cluster_info(cluster_id, user):
+ """Get cluster information.Return a dictionary as below,
+
+ {
+ "id": 1,
+ "adapter_id": 1,
+ "os_version": "CentOS-6.5-x86_64",
+ "name": "cluster_01",
+ "flavor": {
+ "flavor_name": "zzz",
+ "template": "xx.tmpl",
+ "roles": [...]
+ }
+ "os_config": {..},
+ "package_config": {...},
+ "deployed_os_config": {},
+ "deployed_package_config": {},
+ "owner": "xxx"
+ }
+ """
+
+ cluster_info = cluster_db.get_cluster(cluster_id, user=user)
+
+ # convert roles retrieved from db into a list of role names
+ roles_info = cluster_info.setdefault(
+ const.FLAVOR, {}).setdefault(const.ROLES, [])
+ cluster_info[const.FLAVOR][const.ROLES] = \
+ ActionHelper._get_role_names(roles_info)
+
+ # get cluster config info
+ cluster_config = cluster_db.get_cluster_config(cluster_id, user=user)
+ cluster_info.update(cluster_config)
+
+ deploy_config = cluster_db.get_cluster_deployed_config(cluster_id,
+ user=user)
+ cluster_info.update(deploy_config)
+
+ return cluster_info
+
+ @staticmethod
+ def get_hosts_info(cluster_id, hosts_id_list, user):
+ """Get hosts information. Return a dictionary as below,
+
+ {
+ "hosts": {
+ 1($host_id): {
+ "reinstall_os": True,
+ "mac": "xxx",
+ "name": "xxx",
+ "roles": [xxx, yyy]
+ },
+ "networks": {
+ "eth0": {
+ "ip": "192.168.1.1",
+ "netmask": "255.255.255.0",
+ "is_mgmt": True,
+ "is_promiscuous": False,
+ "subnet": "192.168.1.0/24"
+ },
+ "eth1": {...}
+ },
+ "os_config": {},
+ "package_config": {},
+ "deployed_os_config": {},
+ "deployed_package_config": {}
+ },
+ 2: {...},
+ ....
+ }
+ }
+ """
+
+ hosts_info = {}
+ for host_id in hosts_id_list:
+ info = cluster_db.get_cluster_host(cluster_id, host_id, user=user)
+ logging.debug("checking on info %r %r" % (host_id, info))
+
+ info[const.ROLES] = ActionHelper._get_role_names(info[const.ROLES])
+
+ # TODO(grace): Is following line necessary??
+ info.setdefault(const.ROLES, [])
+
+ config = cluster_db.get_cluster_host_config(cluster_id,
+ host_id,
+ user=user)
+ info.update(config)
+
+ networks = info[const.NETWORKS]
+ networks_dict = {}
+ # Convert networks from list to dictionary format
+ for entry in networks:
+ nic_info = {}
+ nic_info = {
+ entry[const.NIC]: {
+ const.IP_ADDR: entry[const.IP_ADDR],
+ const.NETMASK: entry[const.NETMASK],
+ const.MGMT_NIC_FLAG: entry[const.MGMT_NIC_FLAG],
+ const.PROMISCUOUS_FLAG: entry[const.PROMISCUOUS_FLAG],
+ const.SUBNET: entry[const.SUBNET]
+ }
+ }
+ networks_dict.update(nic_info)
+
+ info[const.NETWORKS] = networks_dict
+
+ hosts_info[host_id] = info
+
+ return hosts_info
+
+ @staticmethod
+ def save_deployed_config(deployed_config, user):
+ """Save deployed config."""
+ cluster_config = deployed_config[const.CLUSTER]
+ cluster_id = cluster_config[const.ID]
+ del cluster_config[const.ID]
+
+ cluster_db.update_cluster_deployed_config(cluster_id, user=user,
+ **cluster_config)
+
+ hosts_id_list = deployed_config[const.HOSTS].keys()
+ for host_id in hosts_id_list:
+ config = deployed_config[const.HOSTS][host_id]
+ cluster_db.update_cluster_host_deployed_config(cluster_id,
+ host_id,
+ user=user,
+ **config)
+
+ @staticmethod
+ def update_state(
+ cluster_id, host_id_list, user, **kwargs
+ ):
+ # update all clusterhosts state
+ for host_id in host_id_list:
+ cluster_db.update_cluster_host_state(
+ cluster_id,
+ host_id,
+ user=user,
+ **kwargs
+ )
+
+ # update cluster state
+ cluster_db.update_cluster_state(
+ cluster_id,
+ user=user,
+ **kwargs
+ )
+
+ @staticmethod
+ def delete_cluster(
+ cluster_id, host_id_list, user, delete_underlying_host=False
+ ):
+ """Delete cluster.
+
+ If delete_underlying_host is set, underlying hosts will also
+ be deleted.
+ """
+ if delete_underlying_host:
+ for host_id in host_id_list:
+ host_db.del_host(
+ host_id, True, True, user=user
+ )
+ cluster_db.del_cluster(
+ cluster_id, True, True, user=user
+ )
+
+ @staticmethod
+ def delete_cluster_host(
+ cluster_id, host_id, user, delete_underlying_host=False
+ ):
+ """Delete clusterhost.
+
+ If delete_underlying_host set, also delete underlying host.
+ """
+ if delete_underlying_host:
+ host_db.del_host(
+ host_id, True, True, user=user
+ )
+ cluster_db.del_cluster_host(
+ cluster_id, host_id, True, True, user=user
+ )
+
+ @staticmethod
+ def delete_host(host_id, user):
+ host_db.del_host(
+ host_id, True, True, user=user
+ )
+
+ @staticmethod
+ def host_ready(host_id, from_database_only, user):
+ """Trigger host ready."""
+ host_db.update_host_state_internal(
+ host_id, from_database_only=from_database_only,
+ user=user, ready=True
+ )
+
+ @staticmethod
+ def cluster_host_ready(
+ cluster_id, host_id, from_database_only, user
+ ):
+ """Trigger clusterhost ready."""
+ cluster_db.update_cluster_host_state_internal(
+ cluster_id, host_id, from_database_only=from_database_only,
+ user=user, ready=True
+ )
+
+ @staticmethod
+ def is_cluster_os_ready(cluster_id, user=None):
+ return cluster_db.is_cluster_os_ready(cluster_id, user=user)
+
+ @staticmethod
+ def cluster_ready(cluster_id, from_database_only, user):
+ """Trigger cluster ready."""
+ cluster_db.update_cluster_state_internal(
+ cluster_id, from_database_only=from_database_only,
+ user=user, ready=True
+ )
+
+ @staticmethod
+ def get_machine_IPMI(machine_id, user):
+ machine_info = machine_db.get_machine(machine_id, user=user)
+ return machine_info[const.IPMI_CREDS]