kolla-cli/kollacli/common/utils.py

325 lines
9.4 KiB
Python

# Copyright(c) 2016, Oracle and/or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fcntl
import grp
import logging
import os
import pexpect
import pwd
import six
import sys
import kollacli.i18n as u
from kollacli.api.exceptions import InvalidArgument
from kollacli.api.exceptions import MissingArgument
LOG = logging.getLogger(__name__)
def get_kolla_home():
return os.environ.get("KOLLA_HOME", "/usr/share/kolla/")
def get_kolla_etc():
return os.environ.get("KOLLA_ETC", "/etc/kolla/")
def get_kollacli_home():
return os.environ.get("KOLLA_CLI_HOME", "/usr/share/kolla/kollacli/")
def get_kollacli_etc():
return os.environ.get("KOLLA_CLI_ETC", "/etc/kolla/kollacli/")
def get_group_vars_dir():
return os.path.join(get_kolla_home(), 'ansible/group_vars')
def get_host_vars_dir():
return os.path.join(get_kolla_home(), 'ansible/host_vars')
def get_kolla_log_dir():
return '/var/log/kolla/'
def get_admin_uids():
"""get uid and gid of admin user"""
user_info = pwd.getpwnam(get_admin_user())
uid = user_info.pw_uid
gid = user_info.pw_gid
return uid, gid
def get_kolla_log_file_size():
envvar = 'KOLLA_LOG_FILE_SIZE'
size_str = os.environ.get(envvar, '500000')
try:
size = int(size_str)
except Exception:
raise InvalidArgument(
u._('Environmental variable ({env_var}) is not an '
'integer ({log_size}).')
.format(env_var=envvar, log_size=size_str))
return size
def get_property_list_length():
envvar = 'KOLLA_PROP_LIST_LENGTH'
length_str = os.environ.get(envvar, '50')
try:
length = int(length_str)
except Exception:
raise InvalidArgument(
u._('Environmental variable ({env_var}) is not an '
'integer ({prop_length}).')
.format(env_var=envvar, prop_length=length_str))
return length
def get_admin_user():
return os.environ.get("KOLLA_CLI_ADMIN_USER", "kolla")
def get_setup_user():
return os.environ.get("KOLLA_CLI_SETUP_USER", "root")
def get_ansible_command(playbook=False):
"""get a python2 ansible command
Ansible cannot run yet with python3. If the current default
python is py3, prefix the ansible command with a py2
interpreter.
"""
cmd = 'ansible'
if playbook:
cmd = 'ansible-playbook'
if sys.version_info[0] >= 3:
# running with py3, find a py2 interpreter for ansible
py2_path = None
usr_bin = os.path.join('/', 'usr', 'bin')
for fname in os.listdir(usr_bin):
if (fname.startswith('python2.') and
os.path.isfile(os.path.join(usr_bin, fname))):
suffix = fname.split('.')[1]
if suffix.isdigit():
py2_path = os.path.join(usr_bin, fname)
break
if py2_path is None:
raise Exception(
u._('ansible-playbook requires python2 and no '
'python2 interpreter found in {path}.')
.format(path=usr_bin))
cmd = '%s %s' % (py2_path, os.path.join(usr_bin, cmd))
return cmd
def convert_to_unicode(the_string):
"""convert string to unicode.
This is used to fixup extended ascii chars in strings. these chars cause
errors in json pickle/unpickle.
"""
if the_string is None:
return the_string
return six.u(the_string)
def run_cmd(cmd, print_output=True):
"""run a system command
return:
- err_msg: empty string=command succeeded
not None=command failed
- output: string: all the output of the run command
If the command is an ansible playbook command, record the
output in an ansible log file.
"""
pwd_prompt = '[sudo] password'
err_msg = ''
output = ''
child = None
try:
child = pexpect.spawn(cmd)
sniff = child.read(len(pwd_prompt))
sniff = safe_decode(sniff)
if sniff == pwd_prompt:
output = sniff + '\n'
raise Exception(
u._('Insufficient permissions to run command "{command}".')
.format(command=cmd))
child.maxsize = 1
child.timeout = 86400
for line in child:
line = safe_decode(line)
outline = sniff + line.rstrip()
sniff = ''
output = ''.join([output, outline, '\n'])
if print_output:
LOG.info(outline)
except Exception as e:
err_msg = '%s' % e
finally:
if child:
child.close()
if child.exitstatus != 0:
err_msg = (u._('Command failed. : {error}')
.format(error=err_msg))
return err_msg, output
def change_property(file_path, property_key, property_value, clear=False):
"""change property with a file
file_path: path to property file
property_key: property name
property value: property value
clear: flag to remove property
If clear, and property exists, remove it from the property file.
If clear, and property doesn't exists, nothing is done.
If not clear, and key is not found, the new property will be appended.
If not clear, and key is found, edit property in place.
"""
try:
group_info = grp.getgrnam('kolla')
if not os.path.exists(file_path):
with open(file_path, 'a'):
os.utime(file_path, None)
os.chown(file_path, -1, group_info.gr_gid)
new_contents = []
read_data = sync_read_file(file_path)
lines = read_data.split('\n')
new_line = '%s: "%s"' % (property_key, property_value)
property_key_found = False
last_line_empty = False
for line in lines:
line = line.rstrip()
# yank spurious empty lines
if line:
last_line_empty = False
else:
if last_line_empty:
continue
last_line_empty = True
split_line = line.split(':', 1)
if len(split_line) > 1:
split_key = split_line[0]
split_key.rstrip()
if split_key == property_key:
property_key_found = True
if clear:
# clear existing property
continue
# edit existing property
line = new_line
new_contents.append(line)
if not property_key_found and not clear:
# add new property to file
new_contents.append(new_line)
write_data = '\n'.join(new_contents)
sync_write_file(file_path, write_data)
except Exception as e:
raise e
def sync_read_file(path, mode='r'):
"""synchronously read file
return file data
"""
try:
with open(path, mode) as data_file:
fcntl.flock(data_file, fcntl.LOCK_EX)
data = data_file.read()
except Exception as e:
raise e
return data
def sync_write_file(path, data, mode='w'):
"""synchronously write file"""
try:
with open(path, mode) as data_file:
fcntl.flock(data_file, fcntl.LOCK_EX)
data_file.write(data)
except Exception as e:
raise e
def safe_decode(obj_to_decode):
"""Convert bytes or string to unicode string
Convert either a string or list of strings to
unicode.
"""
if obj_to_decode is None:
return None
new_obj = None
if isinstance(obj_to_decode, list):
new_obj = []
for text in obj_to_decode:
try:
text = text.decode('utf-8')
except AttributeError: # nosec
# py3 will raise if text is already a string
pass
new_obj.append(text)
else:
try:
new_obj = obj_to_decode.decode('utf-8')
except AttributeError: # nosec
# py3 will raise if text is already a string
new_obj = obj_to_decode
return new_obj
def is_string_true(string):
"""Return boolean True if string represents a true value (None is False)"""
true_values = ['yes', 'true']
if string is not None and string.lower() in true_values:
return True
else:
return False
def check_arg(param, param_name, expected_type, none_ok=False, empty_ok=False):
if param is None:
if none_ok:
return
# None arg
raise MissingArgument(param_name)
if (not isinstance(param, bool) and
not param and not empty_ok):
# empty arg
raise MissingArgument(param_name)
if not isinstance(param, expected_type):
# wrong type
raise InvalidArgument(u._('{name} ({param}) is not a {type}')
.format(name=param_name, param=param,
type=expected_type))