daisycloud-core/code/daisy/daisy/api/backends/tecs/common.py

497 lines
19 KiB
Python
Executable File

# Copyright 2013 OpenStack Foundation
# 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.
"""
/install endpoint for tecs API
"""
import os
import copy
import subprocess
import re
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPForbidden
from daisy import i18n
from daisy.common import utils
from daisy.common import exception
import daisy.registry.client.v1.api as registry
import daisy.api.backends.common as daisy_cmn
from daisyclient.v1 import client as daisy_client
import ConfigParser
STR_MASK = '*' * 8
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
daisy_tecs_path = '/var/lib/daisy/tecs/'
tecs_install_path = '/home/tecs_install'
TECS_STATE = {
'INIT': 'init',
'INSTALLING': 'installing',
'ACTIVE': 'active',
'INSTALL_FAILED': 'install-failed',
'UNINSTALLING': 'uninstalling',
'UNINSTALL_FAILED': 'uninstall-failed',
'UPDATING': 'updating',
'UPDATE_FAILED': 'update-failed',
}
def get_daisyclient():
"""Get Daisy client instance."""
config_daisy = ConfigParser.ConfigParser()
config_daisy.read("/etc/daisy/daisy-api.conf")
daisy_port = config_daisy.get("DEFAULT", "bind_port")
args = {'version': 1.0, 'endpoint': 'http://127.0.0.1:' + daisy_port}
return daisy_client.Client(**args)
def mkdir_tecs_install(host_ips=None):
if not host_ips:
cmd = "mkdir -p %s" % tecs_install_path
daisy_cmn.subprocess_call(cmd)
return
for host_ip in host_ips:
cmd = 'clush -S -w %s "mkdir -p %s"' % (host_ip, tecs_install_path)
daisy_cmn.subprocess_call(cmd)
def _get_cluster_network(cluster_networks, network_name):
network = [cn for cn in cluster_networks if cn['name'] == network_name]
if not network or not network[0]:
msg = "network %s is not exist" % (network_name)
raise exception.InvalidNetworkConfig(msg)
else:
return network[0]
def get_host_interface_by_network(host_detail, network_name):
host_detail_info = copy.deepcopy(host_detail)
interface_list = [hi for hi in host_detail_info['interfaces']
for assigned_network in hi['assigned_networks']
if assigned_network and
network_name == assigned_network['name']]
interface = {}
if interface_list:
interface = interface_list[0]
if not interface and 'MANAGEMENT' == network_name:
msg = "network %s of host %s is not exist" % (
network_name, host_detail_info['id'])
raise exception.InvalidNetworkConfig(msg)
return interface
def get_host_network_ip(req, host_detail, cluster_networks, network_name):
interface_network_ip = ''
host_interface = get_host_interface_by_network(host_detail, network_name)
if host_interface:
network = _get_cluster_network(cluster_networks, network_name)
assigned_network = daisy_cmn.get_assigned_network(req,
host_interface['id'],
network['id'])
interface_network_ip = assigned_network['ip']
if not interface_network_ip and 'MANAGEMENT' == network_name:
msg = "%s network ip of host %s can't be empty" % (
network_name, host_detail['id'])
raise exception.InvalidNetworkConfig(msg)
return interface_network_ip
def get_storage_name_ip_dict(req, cluster_id, network_type):
name_ip_list = []
ip_list = []
roles = daisy_cmn.get_cluster_roles_detail(req, cluster_id)
cluster_networks = daisy_cmn.get_cluster_networks_detail(req, cluster_id)
networks_list = [network for network in cluster_networks
if network['network_type'] == network_type]
networks_name_list = [network['name'] for network in networks_list]
for role in roles:
role_hosts = daisy_cmn.get_hosts_of_role(req, role['id'])
for role_host in role_hosts:
host_detail = daisy_cmn.get_host_detail(req, role_host['host_id'])
for network_name in networks_name_list:
ip = get_host_network_ip(req, host_detail, cluster_networks,
network_name)
name_ip_dict = {}
if ip and ip not in ip_list:
ip_list.append(ip)
name_ip_dict.update({host_detail['name'] + '.' +
network_name: ip})
name_ip_list.append(name_ip_dict)
return name_ip_list
def get_network_netmask(cluster_networks, network_name):
network = _get_cluster_network(cluster_networks, network_name)
cidr = network['cidr']
if not cidr:
msg = "cidr of network %s is not exist" % (network_name)
raise exception.InvalidNetworkConfig(msg)
netmask = daisy_cmn.cidr_to_netmask(cidr)
if not netmask:
msg = "netmask of network %s is not exist" % (network_name)
raise exception.InvalidNetworkConfig(msg)
return netmask
# every host only have one gateway
def get_network_gateway(cluster_networks, network_name):
network = _get_cluster_network(cluster_networks, network_name)
gateway = network['gateway']
return gateway
def get_network_cidr(cluster_networks, network_name):
network = _get_cluster_network(cluster_networks, network_name)
cidr = network['cidr']
if not cidr:
msg = "cidr of network %s is not exist" % (network_name)
raise exception.InvalidNetworkConfig(msg)
return cidr
def get_mngt_network_vlan_id(cluster_networks):
mgnt_vlan_id = ""
management_network = [network for network in cluster_networks if network[
'network_type'] == 'MANAGEMENT']
if (not management_network or
not management_network[0] or
# not management_network[0].has_key('vlan_id')):
'vlan_id' not in management_network[0]):
msg = "can't get management network vlan id"
raise exception.InvalidNetworkConfig(msg)
else:
mgnt_vlan_id = management_network[0]['vlan_id']
return mgnt_vlan_id
def get_network_vlan_id(cluster_networks, network_type):
vlan_id = ""
general_network = [network for network in cluster_networks
if network['network_type'] == network_type]
if (not general_network or not general_network[0] or
# not general_network[0].has_key('vlan_id')):
'vlan_id' not in general_network[0]):
msg = "can't get %s network vlan id" % network_type
raise exception.InvalidNetworkConfig(msg)
else:
vlan_id = general_network[0]['vlan_id']
return vlan_id
def sort_interfaces_by_pci(networks, host_detail):
"""
Sort interfaces by pci segment, if interface type is bond,
user the pci of first memeber nic.This function is fix bug for
the name length of ovs virtual port, because if the name length large than
15 characters, the port will create failed.
:param interfaces: interfaces info of the host
:return:
"""
interfaces = eval(host_detail.get('interfaces', None)) \
if isinstance(host_detail, unicode) else \
host_detail.get('interfaces', None)
if not interfaces:
LOG.info("This host has no interfaces info.")
return host_detail
tmp_interfaces = copy.deepcopy(interfaces)
slaves_name_list = []
for interface in tmp_interfaces:
if interface.get('type', None) == "bond" and\
interface.get('slave1', None) and\
interface.get('slave2', None):
slaves_name_list.append(interface['slave1'])
slaves_name_list.append(interface['slave2'])
for interface in interfaces:
if interface.get('name') not in slaves_name_list:
vlan_id_len_list = [len(network['vlan_id'])
for assigned_network in interface.get(
'assigned_networks', [])
for network in networks
if assigned_network.get('name') ==
network.get('name') and network.get('vlan_id')]
max_vlan_id_len = max(vlan_id_len_list) if vlan_id_len_list else 0
interface_name_len = len(interface['name'])
redundant_bit = interface_name_len + max_vlan_id_len - 14
interface['name'] = interface['name'][
redundant_bit:] if redundant_bit > 0 else interface['name']
return host_detail
def check_and_get_tecs_version(daisy_tecs_pkg_path):
tecs_version_pkg_file = ""
get_tecs_version_pkg = "ls %s| grep ^ZXTECS.*\.bin$" % daisy_tecs_pkg_path
obj = subprocess.Popen(get_tecs_version_pkg,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(stdoutput, erroutput) = obj.communicate()
if stdoutput:
tecs_version_pkg_name = stdoutput.split('\n')[0]
tecs_version_pkg_file = daisy_tecs_pkg_path + tecs_version_pkg_name
chmod_for_tecs_version = 'chmod +x %s' % tecs_version_pkg_file
daisy_cmn.subprocess_call(chmod_for_tecs_version)
return tecs_version_pkg_file
def get_service_disk_list(req, params):
try:
service_disks = registry.list_service_disk_metadata(
req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return service_disks
def get_cinder_volume_list(req, params):
try:
cinder_volumes = registry.list_cinder_volume_metadata(
req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return cinder_volumes
def mask_string(unmasked, mask_list=None, replace_list=None):
"""
Replaces words from mask_list with MASK in unmasked string.
If words are needed to be transformed before masking, transformation
could be describe in replace list. For example [("'","'\\''")]
replaces all ' characters with '\\''.
"""
mask_list = mask_list or []
replace_list = replace_list or []
masked = unmasked
for word in sorted(mask_list, lambda x, y: len(y) - len(x)):
if not word:
continue
for before, after in replace_list:
word = word.replace(before, after)
masked = masked.replace(word, STR_MASK)
return masked
def run_scrip(script, ip=None, password=None, msg=None):
try:
_run_scrip(script, ip, password)
except:
msg1 = 'Error occurred during running scripts.'
message = msg1 + msg if msg else msg1
LOG.error(message)
raise HTTPForbidden(explanation=message)
else:
LOG.info('Running scripts successfully!')
def _run_scrip(script, ip=None, password=None):
mask_list = []
repl_list = [("'", "'\\''")]
script = "\n".join(script)
_PIPE = subprocess.PIPE
if ip:
cmd = ["sshpass", "-p", "%s" % password,
"ssh", "-o StrictHostKeyChecking=no",
"%s" % ip, "bash -x"]
else:
cmd = ["bash", "-x"]
environ = os.environ
environ['LANG'] = 'en_US.UTF8'
obj = subprocess.Popen(cmd, stdin=_PIPE, stdout=_PIPE, stderr=_PIPE,
close_fds=True, shell=False, env=environ)
script = "function t(){ exit $? ; } \n trap t ERR \n" + script
out, err = obj.communicate(script)
masked_out = mask_string(out, mask_list, repl_list)
masked_err = mask_string(err, mask_list, repl_list)
if obj.returncode:
pattern = (r'^ssh\:')
if re.search(pattern, err):
LOG.error(_("Network error occured when run script."))
raise exception.NetworkError(masked_err, stdout=out, stderr=err)
else:
msg = ('Failed to run remote script, stdout: %s\nstderr: %s' %
(masked_out, masked_err))
LOG.error(msg)
raise exception.ScriptRuntimeError(msg, stdout=out, stderr=err)
return obj.returncode, out
def inform_provider_cloud_state(context, cluster_id, **kwargs):
params = dict()
daisyclient = get_daisyclient()
cluster = registry.get_cluster_metadata(context, cluster_id)
params['operation'] = kwargs.get('operation')
params['name'] = cluster.get('name')
params['url'] = "http://" + cluster.get('public_vip')
params['provider_ip'] = cluster.get('hwm_ip')
daisyclient.node.cloud_state(**params)
def get_disk_array_nodes_addr(req, cluster_id):
controller_ha_nodes = {}
computer_ips = set()
roles = daisy_cmn.get_cluster_roles_detail(req, cluster_id)
cluster_networks =\
daisy_cmn.get_cluster_networks_detail(req, cluster_id)
for role in roles:
if role['deployment_backend'] != daisy_cmn.tecs_backend_name:
continue
role_hosts = daisy_cmn.get_hosts_of_role(req, role['id'])
for role_host in role_hosts:
# host has installed tecs are exclusive
if (role_host['status'] == TECS_STATE['ACTIVE'] or
role_host['status'] == TECS_STATE['UPDATING'] or
role_host['status'] == TECS_STATE['UPDATE_FAILED']):
continue
host_detail = daisy_cmn.get_host_detail(req,
role_host['host_id'])
host_ip = get_host_network_ip(req,
host_detail,
cluster_networks,
'MANAGEMENT')
if role['name'] == "CONTROLLER_HA":
min_mac = utils.get_host_min_mac(host_detail['interfaces'])
controller_ha_nodes[host_ip] = min_mac
if role['name'] == "COMPUTER":
computer_ips.add(host_ip)
return {'ha': controller_ha_nodes, 'computer': computer_ips}
def get_ctl_ha_nodes_min_mac(req, cluster_id):
'''
ctl_ha_nodes_min_mac = {'host_name1':'min_mac1', ...}
'''
ctl_ha_nodes_min_mac = {}
roles = daisy_cmn.get_cluster_roles_detail(req, cluster_id)
cluster_networks =\
daisy_cmn.get_cluster_networks_detail(req, cluster_id)
for role in roles:
if role['deployment_backend'] != daisy_cmn.tecs_backend_name:
continue
role_hosts = daisy_cmn.get_hosts_of_role(req, role['id'])
for role_host in role_hosts:
# host has installed tecs are exclusive
if (role_host['status'] == TECS_STATE['ACTIVE'] or
role_host['status'] == TECS_STATE['UPDATING'] or
role_host['status'] == TECS_STATE['UPDATE_FAILED']):
continue
host_detail = daisy_cmn.get_host_detail(req,
role_host['host_id'])
host_name = host_detail['name']
if role['name'] == "CONTROLLER_HA":
min_mac = utils.get_host_min_mac(host_detail['interfaces'])
ctl_ha_nodes_min_mac[host_name] = min_mac
return ctl_ha_nodes_min_mac
class TecsShellExector(object):
"""
Class config task before install tecs bin.
"""
def __init__(self, mgnt_ip, task_type, params={}):
self.task_type = task_type
self.mgnt_ip = mgnt_ip
self.params = params
self.clush_cmd = ""
self.rpm_name =\
daisy_cmn.get_rpm_package_by_name(daisy_tecs_path,
'network-configuration')
self.NETCFG_RPM_PATH = daisy_tecs_path + self.rpm_name
self.oper_type = {
'install_rpm': self._install_netcfg_rpm,
'uninstall_rpm': self._uninstall_netcfg_rpm,
'update_rpm': self._update_netcfg_rpm,
}
self.oper_shell = {
'CMD_SSHPASS_PRE': "sshpass -p ossdbg1 %(ssh_ip)s %(cmd)s",
'CMD_RPM_UNINSTALL': "rpm -e network-configuration",
'CMD_RPM_INSTALL': "rpm -i /home/%(rpm)s" % {'rpm': self.rpm_name},
'CMD_RPM_UPDATE': "rpm -U /home/%(rpm)s" % {'rpm': self.rpm_name},
'CMD_RPM_SCP': "scp -o StrictHostKeyChecking=no \
%(path)s root@%(ssh_ip)s:/home" %
{'path': self.NETCFG_RPM_PATH, 'ssh_ip': mgnt_ip}
}
LOG.info(_("<<<Network configuration rpm is %s>>>" % self.rpm_name))
self._execute()
def _uninstall_netcfg_rpm(self):
self.clush_cmd = self.oper_shell['CMD_SSHPASS_PRE'] % \
{"ssh_ip": "ssh -o StrictHostKeyChecking=no " + self.mgnt_ip,
"cmd": self.oper_shell['CMD_RPM_UNINSTALL']}
subprocess.check_output(
self.clush_cmd, shell=True, stderr=subprocess.STDOUT)
def _update_netcfg_rpm(self):
self.clush_cmd = self.oper_shell['CMD_SSHPASS_PRE'] % \
{"ssh_ip": "ssh -o StrictHostKeyChecking=no " + self.mgnt_ip,
"cmd": self.oper_shell['CMD_RPM_UPDATE']}
subprocess.check_output(
self.clush_cmd, shell=True, stderr=subprocess.STDOUT)
def _install_netcfg_rpm(self):
if not os.path.exists(self.NETCFG_RPM_PATH):
LOG.error(_("<<<Rpm %s not exist>>>" % self.NETCFG_RPM_PATH))
return
self.clush_cmd = "%s;%s" % \
(self.oper_shell['CMD_SSHPASS_PRE'] %
{"ssh_ip": "", "cmd": self.oper_shell['CMD_RPM_SCP']},
self.oper_shell['CMD_SSHPASS_PRE'] %
{"ssh_ip": "ssh -o StrictHostKeyChecking=no " +
self.mgnt_ip, "cmd": self.oper_shell['CMD_RPM_INSTALL']})
subprocess.check_output(
self.clush_cmd, shell=True, stderr=subprocess.STDOUT)
def _execute(self):
try:
if not self.task_type or not self.mgnt_ip:
LOG.error(
_("<<<TecsShellExector::execute, input params invalid on \
%s!>>>" % self.mgnt_ip, ))
return
self.oper_type[self.task_type]()
except subprocess.CalledProcessError as e:
LOG.warn(_("<<<TecsShellExector::execute:Execute command failed on\
%s! Reason:%s>>>" % (
self.mgnt_ip, e.output.strip())))
except Exception as e:
LOG.exception(_(e.message))
else:
LOG.info(_("<<<TecsShellExector::execute:Execute command:\
%s,successful on %s!>>>" % (
self.clush_cmd, self.mgnt_ip)))