Timmy modular rewrite
Change-Id: I40e9f4b82dc61b1b658a212500b495697bd1f9ab
This commit is contained in:
parent
a4fed99239
commit
47e8108e0a
62
timmy/cli.py
62
timmy/cli.py
@ -16,7 +16,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from timmy.nodes import Node, NodeManager
|
from timmy.nodes import Node # , NodeManager
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@ -35,22 +35,18 @@ def pretty_run(quiet, msg, f, args=[], kwargs={}):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def add_args(parser, module):
|
||||||
parser = argparse.ArgumentParser(description=('Parallel remote command'
|
parser = module.add_args(parser)
|
||||||
' execution and file'
|
return parser
|
||||||
' manipulation tool'))
|
|
||||||
|
|
||||||
|
def parser_init(add_help=False):
|
||||||
|
desc = 'Parallel remote command execution and file manipulation tool'
|
||||||
|
parser = argparse.ArgumentParser(description=desc, add_help=add_help)
|
||||||
parser.add_argument('-V', '--version', action='store_true',
|
parser.add_argument('-V', '--version', action='store_true',
|
||||||
help='Print Timmy version and exit.')
|
help='Print Timmy version and exit.')
|
||||||
parser.add_argument('-c', '--config',
|
parser.add_argument('-c', '--config',
|
||||||
help='Path to a YAML configuration file.')
|
help='Path to a YAML configuration file.')
|
||||||
parser.add_argument('-j', '--nodes-json',
|
|
||||||
help=('Path to a json file retrieved via'
|
|
||||||
' "fuel node --json". Useful to speed up'
|
|
||||||
' initialization, skips "fuel node" call.'))
|
|
||||||
parser.add_argument('--fuel-ip', help='fuel ip address')
|
|
||||||
parser.add_argument('--fuel-user', help='fuel username')
|
|
||||||
parser.add_argument('--fuel-pass', help='fuel password')
|
|
||||||
parser.add_argument('--fuel-token', help='fuel auth token')
|
|
||||||
parser.add_argument('-o', '--dest-file',
|
parser.add_argument('-o', '--dest-file',
|
||||||
help=('Output filename for the archive in tar.gz'
|
help=('Output filename for the archive in tar.gz'
|
||||||
' format for command outputs and collected'
|
' format for command outputs and collected'
|
||||||
@ -115,8 +111,6 @@ def parse_args():
|
|||||||
help=('Do not use default log collection parameters,'
|
help=('Do not use default log collection parameters,'
|
||||||
' only use what has been provided either via -L'
|
' only use what has been provided either via -L'
|
||||||
' or in rqfile(s). Implies "-l".'))
|
' or in rqfile(s). Implies "-l".'))
|
||||||
parser.add_argument('--logs-no-fuel-remote', action='store_true',
|
|
||||||
help='Do not collect remote logs from Fuel.')
|
|
||||||
parser.add_argument('--logs-speed', type=int, metavar='MBIT/S',
|
parser.add_argument('--logs-speed', type=int, metavar='MBIT/S',
|
||||||
help=('Limit log collection bandwidth to 90%% of the'
|
help=('Limit log collection bandwidth to 90%% of the'
|
||||||
' specified speed in Mbit/s.'))
|
' specified speed in Mbit/s.'))
|
||||||
@ -132,9 +126,6 @@ def parse_args():
|
|||||||
' of a total size larger than locally available'
|
' of a total size larger than locally available'
|
||||||
'. Values lower than 0.3 are not recommended'
|
'. Values lower than 0.3 are not recommended'
|
||||||
' and may result in filling up local disk.'))
|
' and may result in filling up local disk.'))
|
||||||
parser.add_argument('--fuel-proxy',
|
|
||||||
help='use os system proxy variables for fuelclient',
|
|
||||||
action='store_true')
|
|
||||||
parser.add_argument('--only-logs',
|
parser.add_argument('--only-logs',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help=('Only collect logs, do not run commands or'
|
help=('Only collect logs, do not run commands or'
|
||||||
@ -142,8 +133,6 @@ def parse_args():
|
|||||||
parser.add_argument('--fake-logs',
|
parser.add_argument('--fake-logs',
|
||||||
help='Do not collect logs, only calculate size.',
|
help='Do not collect logs, only calculate size.',
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser.add_argument('-x', '--extended', action='store_true',
|
|
||||||
help='Execute extended commands.')
|
|
||||||
parser.add_argument('--no-archive',
|
parser.add_argument('--no-archive',
|
||||||
help=('Do not create results archive. By default,'
|
help=('Do not create results archive. By default,'
|
||||||
' an archive with all outputs and files'
|
' an archive with all outputs and files'
|
||||||
@ -158,7 +147,7 @@ def parse_args():
|
|||||||
' messages. Good for quick runs / "watch" wrap.'
|
' messages. Good for quick runs / "watch" wrap.'
|
||||||
' This option disables any -v parameters.'),
|
' This option disables any -v parameters.'),
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser.add_argument('-m', '--maxthreads', type=int, default=100,
|
parser.add_argument('--maxthreads', type=int, default=100,
|
||||||
metavar='NUMBER',
|
metavar='NUMBER',
|
||||||
help=('Maximum simultaneous nodes for command'
|
help=('Maximum simultaneous nodes for command'
|
||||||
'execution.'))
|
'execution.'))
|
||||||
@ -179,6 +168,9 @@ def parse_args():
|
|||||||
' results. Do not forget to clean up the results'
|
' results. Do not forget to clean up the results'
|
||||||
' manually when using this option.'),
|
' manually when using this option.'),
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
parser.add_argument('-m', '--module', metavar='INVENTORY MODULE',
|
||||||
|
default='fuel',
|
||||||
|
help='Use module to get node data')
|
||||||
parser.add_argument('-v', '--verbose', action='count', default=0,
|
parser.add_argument('-v', '--verbose', action='count', default=0,
|
||||||
help=('This works for -vvvv, -vvv, -vv, -v, -v -v,'
|
help=('This works for -vvvv, -vvv, -vv, -v, -v -v,'
|
||||||
'etc, If no -v then logging.WARNING is '
|
'etc, If no -v then logging.WARNING is '
|
||||||
@ -192,7 +184,13 @@ def parse_args():
|
|||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
parser = parse_args()
|
parser = parser_init()
|
||||||
|
args, unknown = parser.parse_known_args(argv[1:])
|
||||||
|
parser = parser_init(add_help=True)
|
||||||
|
if args.module:
|
||||||
|
inventory = __import__('timmy.modules.%s' % args.module,
|
||||||
|
fromlist=['timmy.modules'])
|
||||||
|
parser = add_args(parser, inventory)
|
||||||
args = parser.parse_args(argv[1:])
|
args = parser.parse_args(argv[1:])
|
||||||
if args.version:
|
if args.version:
|
||||||
print(version)
|
print(version)
|
||||||
@ -210,17 +208,9 @@ def main(argv=None):
|
|||||||
format=FORMAT)
|
format=FORMAT)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
conf = load_conf(args.config)
|
conf = load_conf(args.config)
|
||||||
if args.fuel_ip:
|
if inventory:
|
||||||
conf['fuel_ip'] = args.fuel_ip
|
inventory.add_conf(conf)
|
||||||
if args.fuel_user:
|
inventory.check_args(args, conf)
|
||||||
conf['fuel_user'] = args.fuel_user
|
|
||||||
if args.fuel_pass:
|
|
||||||
conf['fuel_pass'] = args.fuel_pass
|
|
||||||
if args.fuel_token:
|
|
||||||
conf['fuel_api_token'] = args.fuel_token
|
|
||||||
conf['fuelclient'] = False
|
|
||||||
if args.fuel_proxy:
|
|
||||||
conf['fuel_skip_proxy'] = False
|
|
||||||
if args.put or args.command or args.script or args.get:
|
if args.put or args.command or args.script or args.get:
|
||||||
conf['shell_mode'] = True
|
conf['shell_mode'] = True
|
||||||
conf['do_print_results'] = True
|
conf['do_print_results'] = True
|
||||||
@ -235,8 +225,6 @@ def main(argv=None):
|
|||||||
if args.logs_no_default:
|
if args.logs_no_default:
|
||||||
conf['logs_no_default'] = True
|
conf['logs_no_default'] = True
|
||||||
args.logs = True
|
args.logs = True
|
||||||
if args.logs_no_fuel_remote:
|
|
||||||
conf['logs_no_fuel_remote'] = True
|
|
||||||
if args.logs_speed or args.logs_speed_auto:
|
if args.logs_speed or args.logs_speed_auto:
|
||||||
conf['logs_speed_limit'] = True
|
conf['logs_speed_limit'] = True
|
||||||
if args.logs_speed:
|
if args.logs_speed:
|
||||||
@ -296,8 +284,8 @@ def main(argv=None):
|
|||||||
logger.info('Using rqdir: %s, rqfile: %s' %
|
logger.info('Using rqdir: %s, rqfile: %s' %
|
||||||
(conf['rqdir'], conf['rqfile']))
|
(conf['rqdir'], conf['rqfile']))
|
||||||
nm = pretty_run(args.quiet, 'Initializing node data',
|
nm = pretty_run(args.quiet, 'Initializing node data',
|
||||||
NodeManager,
|
inventory.NodeManager,
|
||||||
kwargs={'conf': conf, 'extended': args.extended,
|
kwargs={'conf': conf,
|
||||||
'nodes_json': args.nodes_json})
|
'nodes_json': args.nodes_json})
|
||||||
if args.only_logs or args.logs:
|
if args.only_logs or args.logs:
|
||||||
size = pretty_run(args.quiet, 'Calculating logs size',
|
size = pretty_run(args.quiet, 'Calculating logs size',
|
||||||
|
@ -30,20 +30,6 @@ def load_conf(filename):
|
|||||||
'-lroot', '-oBatchMode=yes']
|
'-lroot', '-oBatchMode=yes']
|
||||||
conf['env_vars'] = ['OPENRC=/root/openrc', 'IPTABLES_STR="iptables -nvL"',
|
conf['env_vars'] = ['OPENRC=/root/openrc', 'IPTABLES_STR="iptables -nvL"',
|
||||||
'LC_ALL="C"', 'LANG="C"']
|
'LC_ALL="C"', 'LANG="C"']
|
||||||
conf['fuel_ip'] = '127.0.0.1'
|
|
||||||
conf['fuel_api_user'] = 'admin'
|
|
||||||
conf['fuel_api_pass'] = 'admin'
|
|
||||||
conf['fuel_api_token'] = None
|
|
||||||
conf['fuel_api_tenant'] = 'admin'
|
|
||||||
conf['fuel_api_port'] = '8000'
|
|
||||||
conf['fuel_api_keystone_port'] = '5000'
|
|
||||||
# The three parameters below are used to override FuelClient, API, CLI auth
|
|
||||||
conf['fuel_user'] = None
|
|
||||||
conf['fuel_pass'] = None
|
|
||||||
conf['fuel_tenant'] = None
|
|
||||||
|
|
||||||
conf['fuelclient'] = True # use fuelclient library by default
|
|
||||||
conf['fuel_skip_proxy'] = True
|
|
||||||
conf['timeout'] = 15
|
conf['timeout'] = 15
|
||||||
conf['prefix'] = 'nice -n 19 ionice -c 3'
|
conf['prefix'] = 'nice -n 19 ionice -c 3'
|
||||||
rqdir = 'rq'
|
rqdir = 'rq'
|
||||||
@ -68,12 +54,6 @@ def load_conf(filename):
|
|||||||
conf['filelists'] = []
|
conf['filelists'] = []
|
||||||
conf['logs'] = []
|
conf['logs'] = []
|
||||||
conf['logs_no_default'] = False # skip logs defined in default.yaml
|
conf['logs_no_default'] = False # skip logs defined in default.yaml
|
||||||
conf['logs_fuel_remote_dir'] = ['/var/log/docker-logs/remote',
|
|
||||||
'/var/log/remote']
|
|
||||||
conf['logs_no_fuel_remote'] = False # do not collect /var/log/remote
|
|
||||||
'''Do not collect from /var/log/remote/<node>
|
|
||||||
if node is in the array of nodes filtered out by soft filter'''
|
|
||||||
conf['logs_exclude_filtered'] = True
|
|
||||||
conf['logs_days'] = 30
|
conf['logs_days'] = 30
|
||||||
conf['logs_speed_limit'] = False # enable speed limiting of log transfers
|
conf['logs_speed_limit'] = False # enable speed limiting of log transfers
|
||||||
conf['logs_speed_default'] = 100 # Mbit/s, used when autodetect fails
|
conf['logs_speed_default'] = 100 # Mbit/s, used when autodetect fails
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
project_name = 'timmy'
|
project_name = 'timmy'
|
||||||
version = '1.20.2'
|
version = '1.20.1'
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
|
0
timmy/modules/__init__.py
Normal file
0
timmy/modules/__init__.py
Normal file
486
timmy/modules/fuel.py
Normal file
486
timmy/modules/fuel.py
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2016 Mirantis, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib2
|
||||||
|
from timmy import tools
|
||||||
|
from timmy.nodes import NodeManager as BaseNodeManager
|
||||||
|
from timmy.nodes import Node as BaseNode
|
||||||
|
|
||||||
|
try:
|
||||||
|
import fuelclient
|
||||||
|
if hasattr(fuelclient, 'connect'):
|
||||||
|
# fuel > 9.0.1
|
||||||
|
from fuelclient import connect as FuelClient
|
||||||
|
FUEL_10 = True
|
||||||
|
else:
|
||||||
|
import fuelclient.client
|
||||||
|
if type(fuelclient.client.APIClient) is fuelclient.client.Client:
|
||||||
|
# fuel 9.0.1 and below
|
||||||
|
from fuelclient.client import Client as FuelClient
|
||||||
|
FUEL_10 = False
|
||||||
|
else:
|
||||||
|
FuelClient = None
|
||||||
|
except:
|
||||||
|
FuelClient = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from fuelclient.client import logger
|
||||||
|
logger.handlers = []
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def add_args(parser):
|
||||||
|
parser.add_argument('--fuel-ip', help='fuel ip address')
|
||||||
|
parser.add_argument('--fuel-user', help='fuel username')
|
||||||
|
parser.add_argument('--fuel-pass', help='fuel password')
|
||||||
|
parser.add_argument('--fuel-token', help='fuel auth token')
|
||||||
|
parser.add_argument('--fuel-logs-no-remote', action='store_true',
|
||||||
|
help='Do not collect remote logs from Fuel.')
|
||||||
|
parser.add_argument('--fuel-proxy',
|
||||||
|
help='use os system proxy variables for fuelclient',
|
||||||
|
action='store_true')
|
||||||
|
parser.add_argument('-j', '--nodes-json',
|
||||||
|
help=('Path to a json file retrieved via'
|
||||||
|
' "fuel node --json". Useful to speed up'
|
||||||
|
' initialization, skips "fuel node" call.'))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def check_args(args, conf):
|
||||||
|
if args.fuel_ip:
|
||||||
|
conf['fuel_ip'] = args.fuel_ip
|
||||||
|
if args.fuel_user:
|
||||||
|
conf['fuel_user'] = args.fuel_user
|
||||||
|
if args.fuel_pass:
|
||||||
|
conf['fuel_pass'] = args.fuel_pass
|
||||||
|
if args.fuel_proxy:
|
||||||
|
conf['fuel_skip_proxy'] = False
|
||||||
|
if args.fuel_token:
|
||||||
|
conf['fuel_api_token'] = args.fuel_token
|
||||||
|
conf['fuelclient'] = False
|
||||||
|
if args.fuel_logs_no_remote:
|
||||||
|
conf['fuel_logs_no_remote'] = True
|
||||||
|
|
||||||
|
|
||||||
|
def add_conf(conf):
|
||||||
|
conf['fuel_ip'] = '127.0.0.1'
|
||||||
|
conf['fuel_api_user'] = 'admin'
|
||||||
|
conf['fuel_api_pass'] = 'admin'
|
||||||
|
conf['fuel_api_token'] = None
|
||||||
|
conf['fuel_api_tenant'] = 'admin'
|
||||||
|
conf['fuel_api_port'] = '8000'
|
||||||
|
conf['fuel_api_keystone_port'] = '5000'
|
||||||
|
# The three parameters below are used to override FuelClient, API, CLI auth
|
||||||
|
conf['fuel_user'] = None
|
||||||
|
conf['fuel_pass'] = None
|
||||||
|
conf['fuel_tenant'] = None
|
||||||
|
|
||||||
|
conf['fuelclient'] = True # use fuelclient library by default
|
||||||
|
conf['fuel_skip_proxy'] = True
|
||||||
|
conf['fuel_logs_remote_dir'] = ['/var/log/docker-logs/remote',
|
||||||
|
'/var/log/remote']
|
||||||
|
conf['fuel_logs_no_remote'] = False # do not collect /var/log/remote
|
||||||
|
'''Do not collect from /var/log/remote/<node>
|
||||||
|
if node is in the array of nodes filtered out by soft filter'''
|
||||||
|
conf['fuel_logs_exclude_filtered'] = True
|
||||||
|
|
||||||
|
|
||||||
|
class Node(BaseNode):
|
||||||
|
def get_release(self):
|
||||||
|
if self.id == 0:
|
||||||
|
cmd = ("awk -F ':' '/release/ {print $2}' "
|
||||||
|
"/etc/nailgun/version.yaml")
|
||||||
|
else:
|
||||||
|
cmd = ("awk -F ':' '/fuel_version/ {print $2}' "
|
||||||
|
"/etc/astute.yaml")
|
||||||
|
release, err, code = tools.ssh_node(ip=self.ip,
|
||||||
|
command=cmd,
|
||||||
|
ssh_opts=self.ssh_opts,
|
||||||
|
timeout=self.timeout,
|
||||||
|
prefix=self.prefix)
|
||||||
|
if code != 0:
|
||||||
|
self.logger.warning('%s: could not determine'
|
||||||
|
' MOS release' % self.repr)
|
||||||
|
release = 'n/a'
|
||||||
|
else:
|
||||||
|
release = release.strip('\n "\'')
|
||||||
|
self.logger.info('%s, MOS release: %s' %
|
||||||
|
(self.repr, release))
|
||||||
|
return release
|
||||||
|
|
||||||
|
def get_roles_hiera(self):
|
||||||
|
def trim_primary(roles):
|
||||||
|
trim_roles = [r for r in roles if not r.startswith('primary-')]
|
||||||
|
trim_roles += [r[8:] for r in roles if r.startswith('primary-')]
|
||||||
|
return trim_roles
|
||||||
|
|
||||||
|
self.logger.debug('%s: roles not defined, trying hiera' % self.repr)
|
||||||
|
cmd = 'hiera roles'
|
||||||
|
outs, errs, code = tools.ssh_node(ip=self.ip,
|
||||||
|
command=cmd,
|
||||||
|
ssh_opts=self.ssh_opts,
|
||||||
|
env_vars=self.env_vars,
|
||||||
|
timeout=self.timeout,
|
||||||
|
prefix=self.prefix)
|
||||||
|
self.check_code(code, 'get_roles_hiera', cmd, errs, [0])
|
||||||
|
if code == 0:
|
||||||
|
try:
|
||||||
|
roles = trim_primary(json.loads(outs))
|
||||||
|
except:
|
||||||
|
self.logger.warning("%s: failed to parse '%s' output as JSON" %
|
||||||
|
(self.repr, cmd))
|
||||||
|
return self.roles
|
||||||
|
self.logger.debug('%s: got roles: %s' % (self.repr, roles))
|
||||||
|
if roles is not None:
|
||||||
|
return roles
|
||||||
|
else:
|
||||||
|
return self.roles
|
||||||
|
else:
|
||||||
|
self.logger.warning("%s: failed to load roles via hiera" %
|
||||||
|
self.repr)
|
||||||
|
self.roles
|
||||||
|
|
||||||
|
def get_cluster_id(self):
|
||||||
|
self.logger.debug('%s: cluster id not defined, trying to determine' %
|
||||||
|
self.repr)
|
||||||
|
astute_file = '/etc/astute.yaml'
|
||||||
|
cmd = ("python -c 'import yaml; a = yaml.load(open(\"%s\")"
|
||||||
|
".read()); print a[\"cluster\"][\"id\"]'" % astute_file)
|
||||||
|
outs, errs, code = tools.ssh_node(ip=self.ip,
|
||||||
|
command=cmd,
|
||||||
|
ssh_opts=self.ssh_opts,
|
||||||
|
env_vars=self.env_vars,
|
||||||
|
timeout=self.timeout,
|
||||||
|
prefix=self.prefix)
|
||||||
|
return int(outs.rstrip('\n')) if code == 0 else None
|
||||||
|
|
||||||
|
def log_item_manipulate(self, item):
|
||||||
|
if self.fuel_logs_no_remote and 'fuel' in self.roles:
|
||||||
|
self.logger.debug('adding Fuel remote logs to exclude list')
|
||||||
|
if 'exclude' not in item:
|
||||||
|
item['exclude'] = []
|
||||||
|
for remote_dir in self.fuel_logs_remote_dir:
|
||||||
|
item['exclude'].append(remote_dir)
|
||||||
|
if 'fuel' in self.roles:
|
||||||
|
for n in self.logs_excluded_nodes:
|
||||||
|
self.logger.debug('removing remote logs for node:%s' % n)
|
||||||
|
if 'exclude' not in item:
|
||||||
|
item['exclude'] = []
|
||||||
|
for remote_dir in self.fuel_logs_remote_dir:
|
||||||
|
ipd = os.path.join(remote_dir, n)
|
||||||
|
item['exclude'].append(ipd)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeManager(BaseNodeManager):
|
||||||
|
def __init__(self, conf, nodes_json=None, logger=None):
|
||||||
|
self.base_init(conf, logger)
|
||||||
|
self.token = self.conf['fuel_api_token']
|
||||||
|
fuelnode = self.fuel_init()
|
||||||
|
self.logs_excluded_nodes = []
|
||||||
|
if FuelClient and conf['fuelclient']:
|
||||||
|
# save os environment variables
|
||||||
|
environ = os.environ
|
||||||
|
try:
|
||||||
|
if self.conf['fuel_skip_proxy']:
|
||||||
|
os.environ['HTTPS_PROXY'] = ''
|
||||||
|
os.environ['HTTP_PROXY'] = ''
|
||||||
|
os.environ['https_proxy'] = ''
|
||||||
|
os.environ['http_proxy'] = ''
|
||||||
|
self.logger.info('Setup fuelclient instance')
|
||||||
|
if FUEL_10:
|
||||||
|
args = {'host': self.conf['fuel_ip'],
|
||||||
|
'port': self.conf['fuel_port']}
|
||||||
|
if self.conf['fuel_user']:
|
||||||
|
args['os_username'] = self.conf['fuel_user']
|
||||||
|
if self.conf['fuel_pass']:
|
||||||
|
args['os_password'] = self.conf['fuel_pass']
|
||||||
|
if self.conf['fuel_tenant']:
|
||||||
|
args['os_tenant_name'] = self.conf['fuel_tenant']
|
||||||
|
self.fuelclient = FuelClient(**args)
|
||||||
|
else:
|
||||||
|
self.fuelclient = FuelClient()
|
||||||
|
if self.conf['fuel_user']:
|
||||||
|
self.fuelclient.username = self.conf['fuel_user']
|
||||||
|
if self.conf['fuel_pass']:
|
||||||
|
self.fuelclient.password = self.conf['fuel_pass']
|
||||||
|
if self.conf['fuel_tenant']:
|
||||||
|
self.fuelclient.tenant_name = self.conf['fuel_tenant']
|
||||||
|
# self.fuelclient.debug_mode(True)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.info('Failed to setup fuelclient instance:%s' % e,
|
||||||
|
exc_info=True)
|
||||||
|
self.fuelclient = None
|
||||||
|
os.environ = environ
|
||||||
|
else:
|
||||||
|
self.logger.info('Skipping setup fuelclient instance')
|
||||||
|
self.fuelclient = None
|
||||||
|
if nodes_json:
|
||||||
|
self.nodes_json = tools.load_json_file(nodes_json)
|
||||||
|
else:
|
||||||
|
if (not self.get_nodes_fuelclient() and
|
||||||
|
not self.get_nodes_api() and
|
||||||
|
not self.get_nodes_cli()):
|
||||||
|
sys.exit(105)
|
||||||
|
self.nodes_init(Node)
|
||||||
|
# get release information for all nodes
|
||||||
|
self.get_release()
|
||||||
|
self.post_init()
|
||||||
|
fuelnode.logs_excluded_nodes = self.logs_excluded_nodes
|
||||||
|
|
||||||
|
def fuel_init(self):
|
||||||
|
if not self.conf['fuel_ip']:
|
||||||
|
self.logger.critical('NodeManager: fuel_ip not set')
|
||||||
|
sys.exit(106)
|
||||||
|
fuelnode = Node(id=0,
|
||||||
|
cluster=0,
|
||||||
|
name='fuel',
|
||||||
|
fqdn='n/a',
|
||||||
|
mac='n/a',
|
||||||
|
os_platform='centos',
|
||||||
|
roles=['fuel'],
|
||||||
|
status='ready',
|
||||||
|
online=True,
|
||||||
|
ip=self.conf['fuel_ip'],
|
||||||
|
conf=self.conf)
|
||||||
|
fuelnode.cluster_repr = ""
|
||||||
|
fuelnode.repr = "fuel"
|
||||||
|
# soft-skip Fuel if it is hard-filtered
|
||||||
|
if not self.filter(fuelnode, self.conf['hard_filter']):
|
||||||
|
fuelnode.filtered_out = True
|
||||||
|
self.nodes[self.conf['fuel_ip']] = fuelnode
|
||||||
|
return fuelnode
|
||||||
|
|
||||||
|
def apply_soft_filter(self):
|
||||||
|
# apply soft-filter on all nodes
|
||||||
|
for node in self.nodes.values():
|
||||||
|
if not self.filter(node, self.conf['soft_filter']):
|
||||||
|
node.filtered_out = True
|
||||||
|
if self.conf['fuel_logs_exclude_filtered']:
|
||||||
|
self.logs_excluded_nodes.append(node.fqdn)
|
||||||
|
self.logs_excluded_nodes.append(node.ip)
|
||||||
|
|
||||||
|
def get_release(self):
|
||||||
|
if (not self.get_release_fuel_client() and
|
||||||
|
not self.get_release_api() and
|
||||||
|
not self.get_release_cli()):
|
||||||
|
self.logger.warning('could not get Fuel and MOS versions')
|
||||||
|
|
||||||
|
def get_nodes_fuelclient(self):
|
||||||
|
if not self.fuelclient:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
self.logger.info('using fuelclient to get nodes json')
|
||||||
|
self.nodes_json = self.fuelclient.get_request('nodes')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(("NodeManager: can't "
|
||||||
|
"get node list from fuel client:\n%s" % (e)),
|
||||||
|
exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_release_api(self):
|
||||||
|
self.logger.info('getting release via API')
|
||||||
|
version_json = self.get_api_request('version')
|
||||||
|
if version_json:
|
||||||
|
version = json.loads(version_json)
|
||||||
|
fuel = self.nodes[self.conf['fuel_ip']]
|
||||||
|
fuel.release = version['release']
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
clusters_json = self.get_api_request('clusters')
|
||||||
|
if clusters_json:
|
||||||
|
clusters = json.loads(clusters_json)
|
||||||
|
self.set_nodes_release(clusters)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_release_fuel_client(self):
|
||||||
|
if not self.fuelclient:
|
||||||
|
return False
|
||||||
|
self.logger.info('getting release via fuelclient')
|
||||||
|
try:
|
||||||
|
v = self.fuelclient.get_request('version')
|
||||||
|
fuel_version = v['release']
|
||||||
|
self.logger.debug('version response:%s' % v)
|
||||||
|
clusters = self.fuelclient.get_request('clusters')
|
||||||
|
self.logger.debug('clusters response:%s' % clusters)
|
||||||
|
except:
|
||||||
|
self.logger.warning(("Can't get fuel version or "
|
||||||
|
"clusters information"))
|
||||||
|
return False
|
||||||
|
self.nodes[self.conf['fuel_ip']].release = fuel_version
|
||||||
|
self.set_nodes_release(clusters)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def auth_token(self):
|
||||||
|
'''Get keystone token to access Nailgun API. Requires Fuel 5+'''
|
||||||
|
if self.token:
|
||||||
|
return True
|
||||||
|
self.logger.info('getting token for Nailgun')
|
||||||
|
v2_body = ('{"auth": {"tenantName": "%s", "passwordCredentials": {'
|
||||||
|
'"username": "%s", "password": "%s"}}}')
|
||||||
|
# v3 not fully implemented yet
|
||||||
|
# v3_body = ('{ "auth": {'
|
||||||
|
# ' "scope": {'
|
||||||
|
# ' "project": {'
|
||||||
|
# ' "name": "%s",'
|
||||||
|
# ' "domain": { "id": "default" }'
|
||||||
|
# ' }'
|
||||||
|
# ' },'
|
||||||
|
# ' "identity": {'
|
||||||
|
# ' "methods": ["password"],'
|
||||||
|
# ' "password": {'
|
||||||
|
# ' "user": {'
|
||||||
|
# ' "name": "%s",'
|
||||||
|
# ' "domain": { "id": "default" },'
|
||||||
|
# ' "password": "%s"'
|
||||||
|
# ' }'
|
||||||
|
# ' }'
|
||||||
|
# ' }'
|
||||||
|
# '}}')
|
||||||
|
# Sticking to v2 API for now because Fuel 9.1 has a custom
|
||||||
|
# domain_id defined in keystone.conf which we do not know.
|
||||||
|
args = {'user': None, 'pass': None, 'tenant': None}
|
||||||
|
for a in args:
|
||||||
|
if self.conf['fuel_%s' % a]:
|
||||||
|
args[a] = self.conf['fuel_%s' % a]
|
||||||
|
else:
|
||||||
|
args[a] = self.conf['fuel_api_%s' % a]
|
||||||
|
req_data = v2_body % (args['tenant'], args['user'], args['pass'])
|
||||||
|
req = urllib2.Request("http://%s:%s/v2.0/tokens" %
|
||||||
|
(self.conf['fuel_ip'],
|
||||||
|
self.conf['fuel_api_keystone_port']), req_data,
|
||||||
|
{'Content-Type': 'application/json'})
|
||||||
|
try:
|
||||||
|
# Disabling v3 token retrieval for now
|
||||||
|
# token = urllib2.urlopen(req).info().getheader('X-Subject-Token')
|
||||||
|
result = urllib2.urlopen(req)
|
||||||
|
resp_body = result.read()
|
||||||
|
resp_json = json.loads(resp_body)
|
||||||
|
token = resp_json['access']['token']['id']
|
||||||
|
self.token = token
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_api_request(self, request):
|
||||||
|
if self.auth_token():
|
||||||
|
url = "http://%s:%s/api/%s" % (self.conf['fuel_ip'],
|
||||||
|
self.conf['fuel_api_port'],
|
||||||
|
request)
|
||||||
|
req = urllib2.Request(url, None, {'X-Auth-Token': self.token})
|
||||||
|
try:
|
||||||
|
result = urllib2.urlopen(req)
|
||||||
|
code = result.getcode()
|
||||||
|
if code == 200:
|
||||||
|
return result.read()
|
||||||
|
else:
|
||||||
|
self.logger.error('NodeManager: cannot get API response'
|
||||||
|
' from %s, code %s' % (url, code))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_nodes_api(self):
|
||||||
|
self.logger.info('using API to get nodes json')
|
||||||
|
nodes_json = self.get_api_request('nodes')
|
||||||
|
if nodes_json:
|
||||||
|
self.nodes_json = json.loads(nodes_json)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_nodes_cli(self):
|
||||||
|
self.logger.info('using CLI to get nodes json')
|
||||||
|
fuelnode = self.nodes[self.conf['fuel_ip']]
|
||||||
|
fuel_node_cmd = ('fuel node list --json --user %s --password %s' %
|
||||||
|
(self.conf['fuel_user'],
|
||||||
|
self.conf['fuel_pass']))
|
||||||
|
nodes_json, err, code = tools.ssh_node(ip=fuelnode.ip,
|
||||||
|
command=fuel_node_cmd,
|
||||||
|
ssh_opts=fuelnode.ssh_opts,
|
||||||
|
timeout=fuelnode.timeout,
|
||||||
|
prefix=fuelnode.prefix)
|
||||||
|
if code != 0:
|
||||||
|
self.logger.warning(('NodeManager: cannot get '
|
||||||
|
'fuel node list from CLI: %s') % err)
|
||||||
|
self.nodes_json = None
|
||||||
|
return False
|
||||||
|
self.nodes_json = json.loads(nodes_json)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_release_cli(self):
|
||||||
|
run_items = []
|
||||||
|
for key, node in self.nodes.items():
|
||||||
|
if not node.filtered_out:
|
||||||
|
run_items.append(tools.RunItem(target=node.get_release,
|
||||||
|
key=key))
|
||||||
|
result = tools.run_batch(run_items, 100, dict_result=True)
|
||||||
|
if result:
|
||||||
|
for key in result:
|
||||||
|
self.nodes[key].release = result[key]
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def nodes_init_fallbacks(self):
|
||||||
|
self.nodes_get_roles_hiera()
|
||||||
|
self.nodes_get_os()
|
||||||
|
self.nodes_get_cluster_ids()
|
||||||
|
|
||||||
|
def nodes_get_roles_hiera(self, maxthreads=100):
|
||||||
|
run_items = []
|
||||||
|
for key, node in self.nodes.items():
|
||||||
|
if all([not node.filtered_out, not node.roles,
|
||||||
|
node.status != 'discover']):
|
||||||
|
run_items.append(tools.RunItem(target=node.get_roles_hiera,
|
||||||
|
key=key))
|
||||||
|
result = tools.run_batch(run_items, maxthreads, dict_result=True)
|
||||||
|
for key in result:
|
||||||
|
if result[key]:
|
||||||
|
self.nodes[key].roles = result[key]
|
||||||
|
|
||||||
|
def nodes_get_cluster_ids(self, maxthreads=100):
|
||||||
|
self.logger.debug('getting cluster ids from nodes')
|
||||||
|
run_items = []
|
||||||
|
for key, node in self.nodes.items():
|
||||||
|
if not node.filtered_out and not node.cluster:
|
||||||
|
run_items.append(tools.RunItem(target=node.get_cluster_id,
|
||||||
|
key=key))
|
||||||
|
result = tools.run_batch(run_items, maxthreads, dict_result=True)
|
||||||
|
for key in result:
|
||||||
|
if result[key] is not None:
|
||||||
|
self.nodes[key].cluster = result[key]
|
||||||
|
|
||||||
|
def set_nodes_release(self, clusters):
|
||||||
|
cldict = {}
|
||||||
|
for cluster in clusters:
|
||||||
|
cldict[cluster['id']] = cluster
|
||||||
|
if cldict:
|
||||||
|
for node in self.nodes.values():
|
||||||
|
if node.cluster:
|
||||||
|
node.release = cldict[node.cluster]['fuel_version']
|
||||||
|
else:
|
||||||
|
# set to n/a or may be fuel_version
|
||||||
|
if node.id != 0:
|
||||||
|
node.release = 'n/a'
|
||||||
|
self.logger.info('%s: release: %s' % (node.repr, node.release))
|
38
timmy/modules/local.py
Normal file
38
timmy/modules/local.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2016 Mirantis, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from timmy.nodes import NodeManager as BaseNodeManager
|
||||||
|
|
||||||
|
|
||||||
|
def add_args(parser):
|
||||||
|
parser.add_argument('-j', '--nodes-json', required=True,
|
||||||
|
help=('Path to a json file containing host info:'
|
||||||
|
' ip, roles, etc.'))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def check_args(args, conf):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def add_conf(conf):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NodeManager(BaseNodeManager):
|
||||||
|
pass
|
421
timmy/nodes.py
421
timmy/nodes.py
@ -19,41 +19,17 @@
|
|||||||
main module
|
main module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime, date, timedelta
|
||||||
import urllib2
|
# import urllib2
|
||||||
import tools
|
import tools
|
||||||
from tools import w_list, run_with_lock
|
from tools import w_list, run_with_lock
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
try:
|
|
||||||
import fuelclient
|
|
||||||
if hasattr(fuelclient, 'connect'):
|
|
||||||
# fuel > 9.0.1
|
|
||||||
from fuelclient import connect as FuelClient
|
|
||||||
FUEL_10 = True
|
|
||||||
else:
|
|
||||||
import fuelclient.client
|
|
||||||
if type(fuelclient.client.APIClient) is fuelclient.client.Client:
|
|
||||||
# fuel 9.0.1 and below
|
|
||||||
from fuelclient.client import Client as FuelClient
|
|
||||||
FUEL_10 = False
|
|
||||||
else:
|
|
||||||
FuelClient = None
|
|
||||||
except:
|
|
||||||
FuelClient = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
from fuelclient.client import logger
|
|
||||||
logger.handlers = []
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
class Node(object):
|
||||||
ckey = 'cmds'
|
ckey = 'cmds'
|
||||||
@ -186,60 +162,6 @@ class Node(object):
|
|||||||
setattr(self, f, [])
|
setattr(self, f, [])
|
||||||
r_apply(conf, p, c_a, k_d, overridden, d, clean=clean)
|
r_apply(conf, p, c_a, k_d, overridden, d, clean=clean)
|
||||||
|
|
||||||
def get_release(self):
|
|
||||||
if self.id == 0:
|
|
||||||
cmd = ("awk -F ':' '/release/ {print $2}' "
|
|
||||||
"/etc/nailgun/version.yaml")
|
|
||||||
else:
|
|
||||||
cmd = ("awk -F ':' '/fuel_version/ {print $2}' "
|
|
||||||
"/etc/astute.yaml")
|
|
||||||
release, err, code = tools.ssh_node(ip=self.ip,
|
|
||||||
command=cmd,
|
|
||||||
ssh_opts=self.ssh_opts,
|
|
||||||
timeout=self.timeout,
|
|
||||||
prefix=self.prefix)
|
|
||||||
if code != 0:
|
|
||||||
self.logger.warning('%s: could not determine'
|
|
||||||
' MOS release' % self.repr)
|
|
||||||
release = 'n/a'
|
|
||||||
else:
|
|
||||||
release = release.strip('\n "\'')
|
|
||||||
self.logger.info('%s, MOS release: %s' %
|
|
||||||
(self.repr, release))
|
|
||||||
return release
|
|
||||||
|
|
||||||
def get_roles_hiera(self):
|
|
||||||
def trim_primary(roles):
|
|
||||||
trim_roles = [r for r in roles if not r.startswith('primary-')]
|
|
||||||
trim_roles += [r[8:] for r in roles if r.startswith('primary-')]
|
|
||||||
return trim_roles
|
|
||||||
|
|
||||||
self.logger.debug('%s: roles not defined, trying hiera' % self.repr)
|
|
||||||
cmd = 'hiera roles'
|
|
||||||
outs, errs, code = tools.ssh_node(ip=self.ip,
|
|
||||||
command=cmd,
|
|
||||||
ssh_opts=self.ssh_opts,
|
|
||||||
env_vars=self.env_vars,
|
|
||||||
timeout=self.timeout,
|
|
||||||
prefix=self.prefix)
|
|
||||||
self.check_code(code, 'get_roles_hiera', cmd, errs, [0])
|
|
||||||
if code == 0:
|
|
||||||
try:
|
|
||||||
roles = trim_primary(json.loads(outs))
|
|
||||||
except:
|
|
||||||
self.logger.warning("%s: failed to parse '%s' output as JSON" %
|
|
||||||
(self.repr, cmd))
|
|
||||||
return self.roles
|
|
||||||
self.logger.debug('%s: got roles: %s' % (self.repr, roles))
|
|
||||||
if roles is not None:
|
|
||||||
return roles
|
|
||||||
else:
|
|
||||||
return self.roles
|
|
||||||
else:
|
|
||||||
self.logger.warning("%s: failed to load roles via hiera" %
|
|
||||||
self.repr)
|
|
||||||
self.roles
|
|
||||||
|
|
||||||
def get_os(self):
|
def get_os(self):
|
||||||
self.logger.debug('%s: os_platform not defined, trying to determine' %
|
self.logger.debug('%s: os_platform not defined, trying to determine' %
|
||||||
self.repr)
|
self.repr)
|
||||||
@ -252,20 +174,6 @@ class Node(object):
|
|||||||
prefix=self.prefix)
|
prefix=self.prefix)
|
||||||
return 'centos' if code else 'ubuntu'
|
return 'centos' if code else 'ubuntu'
|
||||||
|
|
||||||
def get_cluster_id(self):
|
|
||||||
self.logger.debug('%s: cluster id not defined, trying to determine' %
|
|
||||||
self.repr)
|
|
||||||
astute_file = '/etc/astute.yaml'
|
|
||||||
cmd = ("python -c 'import yaml; a = yaml.load(open(\"%s\")"
|
|
||||||
".read()); print a[\"cluster\"][\"id\"]'" % astute_file)
|
|
||||||
outs, errs, code = tools.ssh_node(ip=self.ip,
|
|
||||||
command=cmd,
|
|
||||||
ssh_opts=self.ssh_opts,
|
|
||||||
env_vars=self.env_vars,
|
|
||||||
timeout=self.timeout,
|
|
||||||
prefix=self.prefix)
|
|
||||||
return int(outs.rstrip('\n')) if code == 0 else None
|
|
||||||
|
|
||||||
def check_access(self):
|
def check_access(self):
|
||||||
self.logger.debug('%s: verifyng node access' %
|
self.logger.debug('%s: verifyng node access' %
|
||||||
self.repr)
|
self.repr)
|
||||||
@ -409,7 +317,10 @@ class Node(object):
|
|||||||
recursive=True)
|
recursive=True)
|
||||||
self.check_code(code, 'put_files', 'tools.put_file_scp', errs)
|
self.check_code(code, 'put_files', 'tools.put_file_scp', errs)
|
||||||
|
|
||||||
def logs_populate(self, timeout=5, logs_excluded_nodes=[]):
|
def log_item_manipulate(self, item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def logs_populate(self, timeout=5):
|
||||||
|
|
||||||
def filter_by_re(item, string):
|
def filter_by_re(item, string):
|
||||||
return (('include' not in item or not item['include'] or
|
return (('include' not in item or not item['include'] or
|
||||||
@ -418,20 +329,7 @@ class Node(object):
|
|||||||
any([re.search(e, string) for e in item['exclude']])))
|
any([re.search(e, string) for e in item['exclude']])))
|
||||||
|
|
||||||
for item in self.logs:
|
for item in self.logs:
|
||||||
if self.logs_no_fuel_remote and 'fuel' in self.roles:
|
self.log_item_manipulate(item)
|
||||||
self.logger.debug('adding Fuel remote logs to exclude list')
|
|
||||||
if 'exclude' not in item:
|
|
||||||
item['exclude'] = []
|
|
||||||
for remote_dir in self.logs_fuel_remote_dir:
|
|
||||||
item['exclude'].append(remote_dir)
|
|
||||||
if 'fuel' in self.roles:
|
|
||||||
for n in logs_excluded_nodes:
|
|
||||||
self.logger.debug('removing remote logs for node:%s' % n)
|
|
||||||
if 'exclude' not in item:
|
|
||||||
item['exclude'] = []
|
|
||||||
for remote_dir in self.logs_fuel_remote_dir:
|
|
||||||
ipd = os.path.join(remote_dir, n)
|
|
||||||
item['exclude'].append(ipd)
|
|
||||||
start_str = None
|
start_str = None
|
||||||
if 'start' in item or hasattr(self, 'logs_days'):
|
if 'start' in item or hasattr(self, 'logs_days'):
|
||||||
if hasattr(self, 'logs_days') and 'start' not in item:
|
if hasattr(self, 'logs_days') and 'start' not in item:
|
||||||
@ -524,7 +422,16 @@ class Node(object):
|
|||||||
class NodeManager(object):
|
class NodeManager(object):
|
||||||
"""Class nodes """
|
"""Class nodes """
|
||||||
|
|
||||||
def __init__(self, conf, extended=False, nodes_json=None, logger=None):
|
def __init__(self, conf, nodes_json, logger=None):
|
||||||
|
self.base_init(conf, logger)
|
||||||
|
self.nodes_json = tools.load_json_file(nodes_json)
|
||||||
|
self.nodes_init(Node)
|
||||||
|
self.post_init()
|
||||||
|
|
||||||
|
def nodes_init_fallbacks(self):
|
||||||
|
self.nodes_get_os()
|
||||||
|
|
||||||
|
def base_init(self, conf, logger=None):
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.logger = logger or logging.getLogger(__name__)
|
self.logger = logger or logging.getLogger(__name__)
|
||||||
if conf['outputs_timestamp'] or conf['dir_timestamp']:
|
if conf['outputs_timestamp'] or conf['dir_timestamp']:
|
||||||
@ -545,73 +452,17 @@ class NodeManager(object):
|
|||||||
if self.conf['rqfile']:
|
if self.conf['rqfile']:
|
||||||
self.import_rq()
|
self.import_rq()
|
||||||
self.nodes = {}
|
self.nodes = {}
|
||||||
self.token = self.conf['fuel_api_token']
|
|
||||||
self.fuel_init()
|
def apply_soft_filter(self):
|
||||||
# save os environment variables
|
# apply soft-filter on all nodes
|
||||||
environ = os.environ
|
|
||||||
self.logs_excluded_nodes = []
|
|
||||||
if FuelClient and conf['fuelclient']:
|
|
||||||
try:
|
|
||||||
if self.conf['fuel_skip_proxy']:
|
|
||||||
os.environ['HTTPS_PROXY'] = ''
|
|
||||||
os.environ['HTTP_PROXY'] = ''
|
|
||||||
os.environ['https_proxy'] = ''
|
|
||||||
os.environ['http_proxy'] = ''
|
|
||||||
self.logger.info('Setup fuelclient instance')
|
|
||||||
if FUEL_10:
|
|
||||||
args = {'host': self.conf['fuel_ip'],
|
|
||||||
'port': self.conf['fuel_port']}
|
|
||||||
if self.conf['fuel_user']:
|
|
||||||
args['os_username'] = self.conf['fuel_user']
|
|
||||||
if self.conf['fuel_pass']:
|
|
||||||
args['os_password'] = self.conf['fuel_pass']
|
|
||||||
if self.conf['fuel_tenant']:
|
|
||||||
args['os_tenant_name'] = self.conf['fuel_tenant']
|
|
||||||
self.fuelclient = FuelClient(**args)
|
|
||||||
else:
|
|
||||||
self.fuelclient = FuelClient()
|
|
||||||
if self.conf['fuel_user']:
|
|
||||||
self.fuelclient.username = self.conf['fuel_user']
|
|
||||||
if self.conf['fuel_pass']:
|
|
||||||
self.fuelclient.password = self.conf['fuel_pass']
|
|
||||||
if self.conf['fuel_tenant']:
|
|
||||||
self.fuelclient.tenant_name = self.conf['fuel_tenant']
|
|
||||||
# self.fuelclient.debug_mode(True)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.info('Failed to setup fuelclient instance:%s' % e,
|
|
||||||
exc_info=True)
|
|
||||||
self.fuelclient = None
|
|
||||||
else:
|
|
||||||
self.logger.info('Skipping setup fuelclient instance')
|
|
||||||
self.fuelclient = None
|
|
||||||
if nodes_json:
|
|
||||||
self.nodes_json = tools.load_json_file(nodes_json)
|
|
||||||
else:
|
|
||||||
if (not self.get_nodes_fuelclient() and
|
|
||||||
not self.get_nodes_api() and
|
|
||||||
not self.get_nodes_cli()):
|
|
||||||
sys.exit(105)
|
|
||||||
self.nodes_init()
|
|
||||||
self.nodes_check_access()
|
|
||||||
# get release information for all nodes
|
|
||||||
if (not self.get_release_fuel_client() and
|
|
||||||
not self.get_release_api() and
|
|
||||||
not self.get_release_cli()):
|
|
||||||
self.logger.warning('could not get Fuel and MOS versions')
|
|
||||||
# fallbacks
|
|
||||||
self.nodes_get_roles_hiera()
|
|
||||||
self.nodes_get_os()
|
|
||||||
self.nodes_get_cluster_ids()
|
|
||||||
for node in self.nodes.values():
|
for node in self.nodes.values():
|
||||||
# apply soft-filter on all nodes
|
|
||||||
if not self.filter(node, self.conf['soft_filter']):
|
if not self.filter(node, self.conf['soft_filter']):
|
||||||
node.filtered_out = True
|
node.filtered_out = True
|
||||||
if self.conf['logs_exclude_filtered']:
|
|
||||||
self.logs_excluded_nodes.append(node.fqdn)
|
def post_init(self):
|
||||||
self.logs_excluded_nodes.append(node.ip)
|
|
||||||
self.nodes_reapply_conf()
|
self.nodes_reapply_conf()
|
||||||
|
self.apply_soft_filter()
|
||||||
self.conf_assign_once()
|
self.conf_assign_once()
|
||||||
os.environ = environ
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
def ml_column(matrix, i):
|
def ml_column(matrix, i):
|
||||||
@ -705,201 +556,7 @@ class NodeManager(object):
|
|||||||
for rqfile in self.conf['rqfile']:
|
for rqfile in self.conf['rqfile']:
|
||||||
merge_rq(rqfile, dst)
|
merge_rq(rqfile, dst)
|
||||||
|
|
||||||
def fuel_init(self):
|
def nodes_init(self, NodeClass):
|
||||||
if not self.conf['fuel_ip']:
|
|
||||||
self.logger.critical('NodeManager: fuel_ip not set')
|
|
||||||
sys.exit(106)
|
|
||||||
fuelnode = Node(id=0,
|
|
||||||
cluster=0,
|
|
||||||
name='fuel',
|
|
||||||
fqdn='n/a',
|
|
||||||
mac='n/a',
|
|
||||||
os_platform='centos',
|
|
||||||
roles=['fuel'],
|
|
||||||
status='ready',
|
|
||||||
online=True,
|
|
||||||
ip=self.conf['fuel_ip'],
|
|
||||||
conf=self.conf)
|
|
||||||
fuelnode.cluster_repr = ""
|
|
||||||
fuelnode.repr = "fuel"
|
|
||||||
# soft-skip Fuel if it is hard-filtered
|
|
||||||
if not self.filter(fuelnode, self.conf['hard_filter']):
|
|
||||||
fuelnode.filtered_out = True
|
|
||||||
self.nodes[self.conf['fuel_ip']] = fuelnode
|
|
||||||
|
|
||||||
def get_nodes_fuelclient(self):
|
|
||||||
if not self.fuelclient:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
self.logger.info('using fuelclient to get nodes json')
|
|
||||||
self.nodes_json = self.fuelclient.get_request('nodes')
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(("NodeManager: can't "
|
|
||||||
"get node list from fuel client:\n%s" % (e)),
|
|
||||||
exc_info=True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_release_api(self):
|
|
||||||
self.logger.info('getting release via API')
|
|
||||||
version_json = self.get_api_request('version')
|
|
||||||
if version_json:
|
|
||||||
version = json.loads(version_json)
|
|
||||||
fuel = self.nodes[self.conf['fuel_ip']]
|
|
||||||
fuel.release = version['release']
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
clusters_json = self.get_api_request('clusters')
|
|
||||||
if clusters_json:
|
|
||||||
clusters = json.loads(clusters_json)
|
|
||||||
self.set_nodes_release(clusters)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_release_fuel_client(self):
|
|
||||||
if not self.fuelclient:
|
|
||||||
return False
|
|
||||||
self.logger.info('getting release via fuelclient')
|
|
||||||
try:
|
|
||||||
v = self.fuelclient.get_request('version')
|
|
||||||
fuel_version = v['release']
|
|
||||||
self.logger.debug('version response:%s' % v)
|
|
||||||
clusters = self.fuelclient.get_request('clusters')
|
|
||||||
self.logger.debug('clusters response:%s' % clusters)
|
|
||||||
except:
|
|
||||||
self.logger.warning(("Can't get fuel version or "
|
|
||||||
"clusters information"))
|
|
||||||
return False
|
|
||||||
self.nodes[self.conf['fuel_ip']].release = fuel_version
|
|
||||||
self.set_nodes_release(clusters)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_nodes_release(self, clusters):
|
|
||||||
cldict = {}
|
|
||||||
for cluster in clusters:
|
|
||||||
cldict[cluster['id']] = cluster
|
|
||||||
if cldict:
|
|
||||||
for node in self.nodes.values():
|
|
||||||
if node.cluster:
|
|
||||||
node.release = cldict[node.cluster]['fuel_version']
|
|
||||||
else:
|
|
||||||
# set to n/a or may be fuel_version
|
|
||||||
if node.id != 0:
|
|
||||||
node.release = 'n/a'
|
|
||||||
self.logger.info('%s: release: %s' % (node.repr, node.release))
|
|
||||||
|
|
||||||
def auth_token(self):
|
|
||||||
'''Get keystone token to access Nailgun API. Requires Fuel 5+'''
|
|
||||||
if self.token:
|
|
||||||
return True
|
|
||||||
self.logger.info('getting token for Nailgun')
|
|
||||||
v2_body = ('{"auth": {"tenantName": "%s", "passwordCredentials": {'
|
|
||||||
'"username": "%s", "password": "%s"}}}')
|
|
||||||
# v3 not fully implemented yet
|
|
||||||
# v3_body = ('{ "auth": {'
|
|
||||||
# ' "scope": {'
|
|
||||||
# ' "project": {'
|
|
||||||
# ' "name": "%s",'
|
|
||||||
# ' "domain": { "id": "default" }'
|
|
||||||
# ' }'
|
|
||||||
# ' },'
|
|
||||||
# ' "identity": {'
|
|
||||||
# ' "methods": ["password"],'
|
|
||||||
# ' "password": {'
|
|
||||||
# ' "user": {'
|
|
||||||
# ' "name": "%s",'
|
|
||||||
# ' "domain": { "id": "default" },'
|
|
||||||
# ' "password": "%s"'
|
|
||||||
# ' }'
|
|
||||||
# ' }'
|
|
||||||
# ' }'
|
|
||||||
# '}}')
|
|
||||||
# Sticking to v2 API for now because Fuel 9.1 has a custom
|
|
||||||
# domain_id defined in keystone.conf which we do not know.
|
|
||||||
args = {'user': None, 'pass': None, 'tenant': None}
|
|
||||||
for a in args:
|
|
||||||
if self.conf['fuel_%s' % a]:
|
|
||||||
args[a] = self.conf['fuel_%s' % a]
|
|
||||||
else:
|
|
||||||
args[a] = self.conf['fuel_api_%s' % a]
|
|
||||||
req_data = v2_body % (args['tenant'], args['user'], args['pass'])
|
|
||||||
req = urllib2.Request("http://%s:%s/v2.0/tokens" %
|
|
||||||
(self.conf['fuel_ip'],
|
|
||||||
self.conf['fuel_api_keystone_port']), req_data,
|
|
||||||
{'Content-Type': 'application/json'})
|
|
||||||
try:
|
|
||||||
# Disabling v3 token retrieval for now
|
|
||||||
# token = urllib2.urlopen(req).info().getheader('X-Subject-Token')
|
|
||||||
result = urllib2.urlopen(req)
|
|
||||||
resp_body = result.read()
|
|
||||||
resp_json = json.loads(resp_body)
|
|
||||||
token = resp_json['access']['token']['id']
|
|
||||||
self.token = token
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_api_request(self, request):
|
|
||||||
if self.auth_token():
|
|
||||||
url = "http://%s:%s/api/%s" % (self.conf['fuel_ip'],
|
|
||||||
self.conf['fuel_api_port'],
|
|
||||||
request)
|
|
||||||
req = urllib2.Request(url, None, {'X-Auth-Token': self.token})
|
|
||||||
try:
|
|
||||||
result = urllib2.urlopen(req)
|
|
||||||
code = result.getcode()
|
|
||||||
if code == 200:
|
|
||||||
return result.read()
|
|
||||||
else:
|
|
||||||
self.logger.error('NodeManager: cannot get API response'
|
|
||||||
' from %s, code %s' % (url, code))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_nodes_api(self):
|
|
||||||
self.logger.info('using API to get nodes json')
|
|
||||||
nodes_json = self.get_api_request('nodes')
|
|
||||||
if nodes_json:
|
|
||||||
self.nodes_json = json.loads(nodes_json)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_nodes_cli(self):
|
|
||||||
self.logger.info('using CLI to get nodes json')
|
|
||||||
fuelnode = self.nodes[self.conf['fuel_ip']]
|
|
||||||
fuel_node_cmd = ('fuel node list --json --user %s --password %s' %
|
|
||||||
(self.conf['fuel_user'],
|
|
||||||
self.conf['fuel_pass']))
|
|
||||||
nodes_json, err, code = tools.ssh_node(ip=fuelnode.ip,
|
|
||||||
command=fuel_node_cmd,
|
|
||||||
ssh_opts=fuelnode.ssh_opts,
|
|
||||||
timeout=fuelnode.timeout,
|
|
||||||
prefix=fuelnode.prefix)
|
|
||||||
if code != 0:
|
|
||||||
self.logger.warning(('NodeManager: cannot get '
|
|
||||||
'fuel node list from CLI: %s') % err)
|
|
||||||
self.nodes_json = None
|
|
||||||
return False
|
|
||||||
self.nodes_json = json.loads(nodes_json)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_release_cli(self):
|
|
||||||
run_items = []
|
|
||||||
for key, node in self.nodes.items():
|
|
||||||
if not node.filtered_out:
|
|
||||||
run_items.append(tools.RunItem(target=node.get_release,
|
|
||||||
key=key))
|
|
||||||
result = tools.run_batch(run_items, 100, dict_result=True)
|
|
||||||
if result:
|
|
||||||
for key in result:
|
|
||||||
self.nodes[key].release = result[key]
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def nodes_init(self):
|
|
||||||
for node_data in self.nodes_json:
|
for node_data in self.nodes_json:
|
||||||
params = {'conf': self.conf}
|
params = {'conf': self.conf}
|
||||||
keys = ['id', 'cluster', 'roles', 'fqdn', 'name', 'mac',
|
keys = ['id', 'cluster', 'roles', 'fqdn', 'name', 'mac',
|
||||||
@ -907,9 +564,11 @@ class NodeManager(object):
|
|||||||
for key in keys:
|
for key in keys:
|
||||||
if key in node_data:
|
if key in node_data:
|
||||||
params[key] = node_data[key]
|
params[key] = node_data[key]
|
||||||
node = Node(**params)
|
node = NodeClass(**params)
|
||||||
if self.filter(node, self.conf['hard_filter']):
|
if self.filter(node, self.conf['hard_filter']):
|
||||||
self.nodes[node.ip] = node
|
self.nodes[node.ip] = node
|
||||||
|
self.nodes_check_access()
|
||||||
|
self.nodes_init_fallbacks()
|
||||||
|
|
||||||
def conf_assign_once(self):
|
def conf_assign_once(self):
|
||||||
once = Node.conf_once_prefix
|
once = Node.conf_once_prefix
|
||||||
@ -935,18 +594,6 @@ class NodeManager(object):
|
|||||||
for node in self.nodes.values():
|
for node in self.nodes.values():
|
||||||
node.apply_conf(self.conf)
|
node.apply_conf(self.conf)
|
||||||
|
|
||||||
def nodes_get_roles_hiera(self, maxthreads=100):
|
|
||||||
run_items = []
|
|
||||||
for key, node in self.nodes.items():
|
|
||||||
if all([not node.filtered_out, not node.roles,
|
|
||||||
node.status != 'discover']):
|
|
||||||
run_items.append(tools.RunItem(target=node.get_roles_hiera,
|
|
||||||
key=key))
|
|
||||||
result = tools.run_batch(run_items, maxthreads, dict_result=True)
|
|
||||||
for key in result:
|
|
||||||
if result[key]:
|
|
||||||
self.nodes[key].roles = result[key]
|
|
||||||
|
|
||||||
def nodes_get_os(self, maxthreads=100):
|
def nodes_get_os(self, maxthreads=100):
|
||||||
run_items = []
|
run_items = []
|
||||||
for key, node in self.nodes.items():
|
for key, node in self.nodes.items():
|
||||||
@ -957,18 +604,6 @@ class NodeManager(object):
|
|||||||
if result[key]:
|
if result[key]:
|
||||||
self.nodes[key].os_platform = result[key]
|
self.nodes[key].os_platform = result[key]
|
||||||
|
|
||||||
def nodes_get_cluster_ids(self, maxthreads=100):
|
|
||||||
self.logger.debug('getting cluster ids from nodes')
|
|
||||||
run_items = []
|
|
||||||
for key, node in self.nodes.items():
|
|
||||||
if not node.filtered_out and not node.cluster:
|
|
||||||
run_items.append(tools.RunItem(target=node.get_cluster_id,
|
|
||||||
key=key))
|
|
||||||
result = tools.run_batch(run_items, maxthreads, dict_result=True)
|
|
||||||
for key in result:
|
|
||||||
if result[key] is not None:
|
|
||||||
self.nodes[key].cluster = result[key]
|
|
||||||
|
|
||||||
def nodes_check_access(self, maxthreads=100):
|
def nodes_check_access(self, maxthreads=100):
|
||||||
self.logger.debug('checking if nodes are accessible')
|
self.logger.debug('checking if nodes are accessible')
|
||||||
run_items = []
|
run_items = []
|
||||||
@ -1025,10 +660,8 @@ class NodeManager(object):
|
|||||||
run_items = []
|
run_items = []
|
||||||
for key, node in self.nodes.items():
|
for key, node in self.nodes.items():
|
||||||
if not node.filtered_out:
|
if not node.filtered_out:
|
||||||
args = {'timeout': timeout,
|
|
||||||
'logs_excluded_nodes': self.logs_excluded_nodes}
|
|
||||||
run_items.append(tools.RunItem(target=node.logs_populate,
|
run_items.append(tools.RunItem(target=node.logs_populate,
|
||||||
args=args,
|
args={'timeout': timeout},
|
||||||
key=key))
|
key=key))
|
||||||
result = tools.run_batch(run_items, maxthreads, dict_result=True)
|
result = tools.run_batch(run_items, maxthreads, dict_result=True)
|
||||||
for key in result:
|
for key in result:
|
||||||
|
@ -54,6 +54,8 @@ def interrupt_wrapper(f):
|
|||||||
logger.warning('Interrupted, exiting.')
|
logger.warning('Interrupted, exiting.')
|
||||||
sys.exit(signal.SIGINT)
|
sys.exit(signal.SIGINT)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if not logger.handlers:
|
||||||
|
logging.basicConfig()
|
||||||
logger.error('Error: %s' % e, exc_info=True)
|
logger.error('Error: %s' % e, exc_info=True)
|
||||||
for k in dir(e):
|
for k in dir(e):
|
||||||
'''debug: print all exception attrs except internal
|
'''debug: print all exception attrs except internal
|
||||||
|
Loading…
x
Reference in New Issue
Block a user