1931 lines
78 KiB
Python
1931 lines
78 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
# coding=utf-8
|
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
# Copyright 2013 International Business Machines Corporation
|
|
# 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.
|
|
#
|
|
# Copyright (c) 2018 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
"""Conduct all activity related Inventory.
|
|
|
|
A single instance of :py:class:`inventory.conductor.manager.ConductorManager`
|
|
is created within the inventory-conductor process, and is responsible for
|
|
performing actions for hosts managed by inventory.
|
|
|
|
Commands are received via RPC calls.
|
|
"""
|
|
|
|
import grp
|
|
import os
|
|
import oslo_messaging as messaging
|
|
import pwd
|
|
import socket
|
|
import subprocess
|
|
import tsconfig.tsconfig as tsc
|
|
|
|
from fm_api import constants as fm_constants
|
|
from fm_api import fm_api
|
|
from futurist import periodics
|
|
from inventory.agent import rpcapi as agent_rpcapi
|
|
from inventory.api.controllers.v1 import cpu_utils
|
|
from inventory.api.controllers.v1 import utils
|
|
from inventory.common import constants
|
|
from inventory.common import exception
|
|
from inventory.common import fm
|
|
from inventory.common.i18n import _
|
|
from inventory.common import k_host
|
|
from inventory.common import k_lldp
|
|
from inventory.common import mtce_api
|
|
from inventory.common import rpc as inventory_oslo_rpc
|
|
from inventory.common import utils as cutils
|
|
from inventory.conductor import base_manager
|
|
from inventory.conductor import openstack
|
|
from inventory.db import api as dbapi
|
|
from inventory import objects
|
|
from inventory.systemconfig import plugin as systemconfig_plugin
|
|
from netaddr import IPAddress
|
|
from netaddr import IPNetwork
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
|
|
MANAGER_TOPIC = 'inventory.conductor_manager'
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
conductor_opts = [
|
|
cfg.StrOpt('api_url',
|
|
default=None,
|
|
help=('Url of Inventory API service. If not set Inventory can '
|
|
'get current value from Keystone service catalog.')),
|
|
cfg.IntOpt('audit_interval',
|
|
default=60,
|
|
help='Interval to run conductor audit'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(conductor_opts, 'conductor')
|
|
MTC_ADDRESS = 'localhost'
|
|
MTC_PORT = 2112
|
|
|
|
|
|
class ConductorManager(base_manager.BaseConductorManager):
|
|
"""Inventory Conductor service main class."""
|
|
|
|
# Must be in sync with rpcapi.ConductorAPI's
|
|
RPC_API_VERSION = '1.0'
|
|
my_host_id = None
|
|
|
|
target = messaging.Target(version=RPC_API_VERSION)
|
|
|
|
def __init__(self, host, topic):
|
|
super(ConductorManager, self).__init__(host, topic)
|
|
self.dbapi = None
|
|
self.fm_api = None
|
|
self.fm_log = None
|
|
self.sc_op = None
|
|
|
|
self._openstack = None
|
|
self._api_token = None
|
|
self._mtc_address = MTC_ADDRESS
|
|
self._mtc_port = MTC_PORT
|
|
|
|
def start(self):
|
|
self._start()
|
|
LOG.info("Start inventory-conductor")
|
|
|
|
def init_host(self, admin_context=None):
|
|
super(ConductorManager, self).init_host(admin_context)
|
|
self._start(admin_context)
|
|
|
|
def del_host(self, deregister=True):
|
|
return
|
|
|
|
def _start(self, context=None):
|
|
self.dbapi = dbapi.get_instance()
|
|
self.fm_api = fm_api.FaultAPIs()
|
|
self.fm_log = fm.FmCustomerLog()
|
|
self.sc_op = systemconfig_plugin.SystemConfigPlugin(
|
|
invoke_kwds={'context': context})
|
|
self._openstack = openstack.OpenStackOperator(self.dbapi)
|
|
|
|
# create /var/run/inventory if required. On DOR, the manifests
|
|
# may not run to create this volatile directory.
|
|
self._create_volatile_dir()
|
|
|
|
system = self._populate_default_system(context)
|
|
|
|
inventory_oslo_rpc.init(cfg.CONF)
|
|
LOG.info("inventory-conductor start system=%s" % system.as_dict())
|
|
|
|
def periodic_tasks(self, context, raise_on_error=False):
|
|
"""Periodic tasks are run at pre-specified intervals. """
|
|
return self.run_periodic_tasks(context, raise_on_error=raise_on_error)
|
|
|
|
@periodics.periodic(spacing=CONF.conductor.audit_interval)
|
|
def _conductor_audit(self, context):
|
|
# periodically, perform audit of inventory
|
|
LOG.info("Inventory Conductor running periodic audit task.")
|
|
|
|
system = self._populate_default_system(context)
|
|
LOG.info("Inventory Conductor from systemconfig system=%s" %
|
|
system.as_dict())
|
|
|
|
hosts = objects.Host.list(context)
|
|
|
|
for host in hosts:
|
|
self._audit_install_states(host)
|
|
|
|
if not host.personality:
|
|
continue
|
|
# audit of configured hosts
|
|
self._audit_host_action(host)
|
|
|
|
LOG.debug("Inventory Conductor audited hosts=%s" % hosts)
|
|
|
|
@staticmethod
|
|
def _create_volatile_dir():
|
|
"""Create the volatile directory required for inventory service"""
|
|
if not os.path.isdir(constants.INVENTORY_LOCK_PATH):
|
|
try:
|
|
uid = pwd.getpwnam(constants.INVENTORY_USERNAME).pw_uid
|
|
gid = grp.getgrnam(constants.INVENTORY_GRPNAME).gr_gid
|
|
os.makedirs(constants.INVENTORY_LOCK_PATH)
|
|
os.chown(constants.INVENTORY_LOCK_PATH, uid, gid)
|
|
LOG.info("Created directory=%s" %
|
|
constants.INVENTORY_LOCK_PATH)
|
|
except OSError as e:
|
|
LOG.exception("makedir %s OSError=%s encountered" %
|
|
(constants.INVENTORY_LOCK_PATH, e))
|
|
pass
|
|
|
|
def _populate_default_system(self, context):
|
|
"""Populate the default system tables"""
|
|
|
|
try:
|
|
system = self.dbapi.system_get_one()
|
|
# TODO(sc) return system # system already configured
|
|
except exception.NotFound:
|
|
pass # create default system
|
|
|
|
# Get the system from systemconfig
|
|
system = self.sc_op.system_get_one()
|
|
LOG.info("system retrieved from systemconfig=%s" % system.as_dict())
|
|
|
|
if not system:
|
|
# The audit will need to populate system
|
|
return
|
|
|
|
values = {
|
|
'uuid': system.uuid,
|
|
'name': system.name,
|
|
'system_mode': system.system_mode,
|
|
'region_name': system.region_name,
|
|
'software_version': cutils.get_sw_version(),
|
|
'capabilities': {}}
|
|
|
|
try:
|
|
system = self.dbapi.system_create(values)
|
|
except exception.SystemAlreadyExists:
|
|
system = self.dbapi.system_update(system.uuid, values)
|
|
|
|
return system
|
|
|
|
def _using_static_ip(self, ihost, personality=None, hostname=None):
|
|
using_static = False
|
|
if ihost:
|
|
ipersonality = ihost['personality']
|
|
ihostname = ihost['hostname'] or ""
|
|
else:
|
|
ipersonality = personality
|
|
ihostname = hostname or ""
|
|
|
|
if ipersonality and ipersonality == k_host.CONTROLLER:
|
|
using_static = True
|
|
elif ipersonality and ipersonality == k_host.STORAGE:
|
|
# only storage-0 and storage-1 have static (later storage-2)
|
|
if (ihostname[:len(k_host.STORAGE_0_HOSTNAME)] in
|
|
[k_host.STORAGE_0_HOSTNAME,
|
|
k_host.STORAGE_1_HOSTNAME]):
|
|
using_static = True
|
|
|
|
return using_static
|
|
|
|
def handle_dhcp_lease(self, context, tags, mac, ip_address, cid=None):
|
|
"""Synchronously, have a conductor handle a DHCP lease update.
|
|
|
|
Handling depends on the interface:
|
|
- management interface: do nothing
|
|
- infrastructure interface: do nothing
|
|
- pxeboot interface: create i_host
|
|
|
|
:param cid:
|
|
:param context: request context.
|
|
:param tags: specifies the interface type (mgmt or infra)
|
|
:param mac: MAC for the lease
|
|
:param ip_address: IP address for the lease
|
|
"""
|
|
|
|
LOG.info("receiving dhcp_lease: %s %s %s %s %s" %
|
|
(context, tags, mac, ip_address, cid))
|
|
# Get the first field from the tags
|
|
first_tag = tags.split()[0]
|
|
|
|
if 'pxeboot' == first_tag:
|
|
mgmt_network = \
|
|
self.sc_op.network_get_by_type(
|
|
constants.NETWORK_TYPE_MGMT)
|
|
if not mgmt_network.dynamic:
|
|
return
|
|
|
|
# This is a DHCP lease for a node on the pxeboot network
|
|
# Create the ihost (if necessary).
|
|
ihost_dict = {'mgmt_mac': mac}
|
|
self.create_host(context, ihost_dict, reason='dhcp pxeboot')
|
|
|
|
def handle_dhcp_lease_from_clone(self, context, mac):
|
|
"""Handle dhcp request from a cloned controller-1.
|
|
If MAC address in DB is still set to well known
|
|
clone label, then this is the first boot of the
|
|
other controller. Real MAC address from PXE request
|
|
is updated in the DB.
|
|
"""
|
|
controller_hosts = \
|
|
self.dbapi.host_get_by_personality(k_host.CONTROLLER)
|
|
for host in controller_hosts:
|
|
if (constants.CLONE_ISO_MAC in host.mgmt_mac and
|
|
host.personality == k_host.CONTROLLER and
|
|
host.administrative == k_host.ADMIN_LOCKED):
|
|
LOG.info("create_host (clone): Host found: {}:{}:{}->{}"
|
|
.format(host.hostname, host.personality,
|
|
host.mgmt_mac, mac))
|
|
values = {'mgmt_mac': mac}
|
|
self.dbapi.host_update(host.uuid, values)
|
|
host.mgmt_mac = mac
|
|
self._configure_controller_host(context, host)
|
|
if host.personality and host.hostname:
|
|
ihost_mtc = host.as_dict()
|
|
ihost_mtc['operation'] = 'modify'
|
|
ihost_mtc = cutils.removekeys_nonmtce(ihost_mtc)
|
|
mtce_api.host_modify(
|
|
self._api_token, self._mtc_address,
|
|
self._mtc_port, ihost_mtc,
|
|
constants.MTC_DEFAULT_TIMEOUT_IN_SECS)
|
|
return host
|
|
return None
|
|
|
|
def create_host(self, context, values, reason=None):
|
|
"""Create an ihost with the supplied data.
|
|
|
|
This method allows an ihost to be created.
|
|
|
|
:param reason:
|
|
:param context: an admin context
|
|
:param values: initial values for new ihost object
|
|
:returns: updated ihost object, including all fields.
|
|
"""
|
|
|
|
if 'mgmt_mac' not in values:
|
|
raise exception.InventoryException(_(
|
|
"Invalid method call: create_host requires mgmt_mac."))
|
|
|
|
try:
|
|
mgmt_update_required = False
|
|
mac = values['mgmt_mac']
|
|
mac = mac.rstrip()
|
|
mac = cutils.validate_and_normalize_mac(mac)
|
|
ihost = self.dbapi.host_get_by_mgmt_mac(mac)
|
|
LOG.info("Not creating ihost for mac: %s because it "
|
|
"already exists with uuid: %s" % (values['mgmt_mac'],
|
|
ihost['uuid']))
|
|
mgmt_ip = values.get('mgmt_ip') or ""
|
|
|
|
if mgmt_ip and not ihost.mgmt_ip:
|
|
LOG.info("%s create_host setting mgmt_ip to %s" %
|
|
(ihost.uuid, mgmt_ip))
|
|
mgmt_update_required = True
|
|
elif mgmt_ip and ihost.mgmt_ip and \
|
|
(ihost.mgmt_ip.strip() != mgmt_ip.strip()):
|
|
# Changing the management IP on an already configured
|
|
# host should not occur nor be allowed.
|
|
LOG.error("DANGER %s create_host mgmt_ip dnsmasq change "
|
|
"detected from %s to %s." %
|
|
(ihost.uuid, ihost.mgmt_ip, mgmt_ip))
|
|
|
|
if mgmt_update_required:
|
|
ihost = self.dbapi.host_update(ihost.uuid, values)
|
|
|
|
if ihost.personality and ihost.hostname:
|
|
ihost_mtc = ihost.as_dict()
|
|
ihost_mtc['operation'] = 'modify'
|
|
ihost_mtc = cutils.removekeys_nonmtce(ihost_mtc)
|
|
LOG.info("%s create_host update mtce %s " %
|
|
(ihost.hostname, ihost_mtc))
|
|
mtce_api.host_modify(
|
|
self._api_token, self._mtc_address, self._mtc_port,
|
|
ihost_mtc,
|
|
constants.MTC_DEFAULT_TIMEOUT_IN_SECS)
|
|
|
|
return ihost
|
|
except exception.HostNotFound:
|
|
# If host is not found, check if this is cloning scenario.
|
|
# If yes, update management MAC in the DB and create PXE config.
|
|
clone_host = self.handle_dhcp_lease_from_clone(context, mac)
|
|
if clone_host:
|
|
return clone_host
|
|
|
|
# assign default system
|
|
system = self.dbapi.system_get_one()
|
|
values.update({'system_id': system.id})
|
|
values.update({k_host.HOST_ACTION_STATE:
|
|
k_host.HAS_REINSTALLING})
|
|
|
|
# get tboot value from the active controller
|
|
active_controller = None
|
|
hosts = self.dbapi.host_get_by_personality(k_host.CONTROLLER)
|
|
for h in hosts:
|
|
if utils.is_host_active_controller(h):
|
|
active_controller = h
|
|
break
|
|
|
|
if active_controller is not None:
|
|
tboot_value = active_controller.get('tboot')
|
|
if tboot_value is not None:
|
|
values.update({'tboot': tboot_value})
|
|
|
|
host = objects.Host(context, **values).create()
|
|
|
|
# A host is being created, generate discovery log.
|
|
self._log_host_create(host, reason)
|
|
|
|
ihost_id = host.get('uuid')
|
|
LOG.info("RPC create_host called and created ihost %s." % ihost_id)
|
|
|
|
return host
|
|
|
|
def update_host(self, context, ihost_obj):
|
|
"""Update an ihost with the supplied data.
|
|
|
|
This method allows an ihost to be updated.
|
|
|
|
:param context: an admin context
|
|
:param ihost_obj: a changed (but not saved) ihost object
|
|
:returns: updated ihost object, including all fields.
|
|
"""
|
|
|
|
delta = ihost_obj.obj_what_changed()
|
|
if ('id' in delta) or ('uuid' in delta):
|
|
raise exception.InventoryException(_(
|
|
"Invalid method call: update_host cannot change id or uuid "))
|
|
|
|
ihost_obj.save(context)
|
|
return ihost_obj
|
|
|
|
def _dnsmasq_host_entry_to_string(self, ip_addr, hostname,
|
|
mac_addr=None, cid=None):
|
|
if IPNetwork(ip_addr).version == constants.IPV6_FAMILY:
|
|
ip_addr = "[%s]" % ip_addr
|
|
if cid:
|
|
line = "id:%s,%s,%s,1d\n" % (cid, hostname, ip_addr)
|
|
elif mac_addr:
|
|
line = "%s,%s,%s,1d\n" % (mac_addr, hostname, ip_addr)
|
|
else:
|
|
line = "%s,%s\n" % (hostname, ip_addr)
|
|
return line
|
|
|
|
def _dnsmasq_addn_host_entry_to_string(self, ip_addr, hostname,
|
|
aliases=[]):
|
|
line = "%s %s" % (ip_addr, hostname)
|
|
for alias in aliases:
|
|
line = "%s %s" % (line, alias)
|
|
line = "%s\n" % line
|
|
return line
|
|
|
|
def get_my_host_id(self):
|
|
if not ConductorManager.my_host_id:
|
|
local_hostname = socket.gethostname()
|
|
controller = self.dbapi.host_get(local_hostname)
|
|
ConductorManager.my_host_id = controller['id']
|
|
return ConductorManager.my_host_id
|
|
|
|
def get_dhcp_server_duid(self):
|
|
"""Retrieves the server DUID from the local DHCP server lease file."""
|
|
lease_filename = tsc.CONFIG_PATH + 'dnsmasq.leases'
|
|
with open(lease_filename, 'r') as lease_file:
|
|
for columns in (line.strip().split() for line in lease_file):
|
|
if len(columns) != 2:
|
|
continue
|
|
keyword, value = columns
|
|
if keyword.lower() == "duid":
|
|
return value
|
|
|
|
def _dhcp_release(self, interface, ip_address, mac_address, cid=None):
|
|
"""Release a given DHCP lease"""
|
|
params = [interface, ip_address, mac_address]
|
|
if cid:
|
|
params += [cid]
|
|
if IPAddress(ip_address).version == 6:
|
|
params = ["--ip", ip_address,
|
|
"--iface", interface,
|
|
"--server-id", self.get_dhcp_server_duid(),
|
|
"--client-id", cid,
|
|
"--iaid", str(cutils.get_dhcp_client_iaid(mac_address))]
|
|
LOG.warning("Invoking dhcp_release6 for {}".format(params))
|
|
subprocess.call(["dhcp_release6"] + params)
|
|
else:
|
|
LOG.warning("Invoking dhcp_release for {}".format(params))
|
|
subprocess.call(["dhcp_release"] + params)
|
|
|
|
def _find_networktype_for_address(self, ip_address):
|
|
LOG.info("SC to be queried from systemconfig")
|
|
# TODO(sc) query from systemconfig
|
|
|
|
def _find_local_interface_name(self, network_type):
|
|
"""Lookup the local interface name for a given network type."""
|
|
host_id = self.get_my_host_id()
|
|
interface_list = self.dbapi.iinterface_get_all(host_id, expunge=True)
|
|
ifaces = dict((i['ifname'], i) for i in interface_list)
|
|
port_list = self.dbapi.port_get_all(host_id)
|
|
ports = dict((p['interface_id'], p) for p in port_list)
|
|
for interface in interface_list:
|
|
if interface.networktype == network_type:
|
|
return cutils.get_interface_os_ifname(interface, ifaces, ports)
|
|
|
|
def _find_local_mgmt_interface_vlan_id(self):
|
|
"""Lookup the local interface name for a given network type."""
|
|
host_id = self.get_my_host_id()
|
|
interface_list = self.dbapi.iinterface_get_all(host_id, expunge=True)
|
|
for interface in interface_list:
|
|
if interface.networktype == constants.NETWORK_TYPE_MGMT:
|
|
if 'vlan_id' not in interface:
|
|
return 0
|
|
else:
|
|
return interface['vlan_id']
|
|
|
|
def _remove_leases_by_mac_address(self, mac_address):
|
|
"""Remove any leases that were added without a CID that we were not
|
|
able to delete. This is specifically looking for leases on the pxeboot
|
|
network that may still be present but will also handle the unlikely
|
|
event of deleting an old host during an upgrade. Hosts on previous
|
|
releases did not register a CID on the mgmt interface.
|
|
"""
|
|
lease_filename = tsc.CONFIG_PATH + 'dnsmasq.leases'
|
|
try:
|
|
with open(lease_filename, 'r') as lease_file:
|
|
for columns in (line.strip().split() for line in lease_file):
|
|
if len(columns) != 5:
|
|
continue
|
|
timestamp, address, ip_address, hostname, cid = columns
|
|
if address != mac_address:
|
|
continue
|
|
network_type = self._find_networktype_for_address(
|
|
ip_address)
|
|
if not network_type:
|
|
# Not one of our managed networks
|
|
LOG.warning("Lease for unknown network found in "
|
|
"dnsmasq.leases file: {}".format(columns))
|
|
continue
|
|
interface_name = self._find_local_interface_name(
|
|
network_type
|
|
)
|
|
self._dhcp_release(interface_name, ip_address, mac_address)
|
|
except Exception as e:
|
|
LOG.error("Failed to remove leases for %s: %s" % (mac_address,
|
|
str(e)))
|
|
|
|
def configure_host(self, context, host_obj,
|
|
do_compute_apply=False):
|
|
"""Configure a host.
|
|
|
|
:param context: an admin context
|
|
:param host_obj: the host object
|
|
:param do_compute_apply: configure the compute subfunctions of the host
|
|
"""
|
|
|
|
LOG.info("rpc conductor configure_host %s" % host_obj.uuid)
|
|
|
|
# Request systemconfig plugin to configure_host
|
|
sc_host = self.sc_op.host_configure(
|
|
host_uuid=host_obj.uuid,
|
|
do_compute_apply=do_compute_apply)
|
|
|
|
LOG.info("sc_op sc_host=%s" % sc_host)
|
|
|
|
if sc_host:
|
|
return sc_host.as_dict()
|
|
|
|
def unconfigure_host(self, context, host_obj):
|
|
"""Unconfigure a host.
|
|
|
|
:param context: an admin context.
|
|
:param host_obj: a host object.
|
|
"""
|
|
LOG.info("unconfigure_host %s." % host_obj.uuid)
|
|
|
|
# Request systemconfig plugin to unconfigure_host
|
|
self.sc_op.host_unconfigure(host_obj.uuid)
|
|
|
|
def port_update_by_host(self, context,
|
|
host_uuid, inic_dict_array):
|
|
"""Create iports for an ihost with the supplied data.
|
|
|
|
This method allows records for iports for ihost to be created.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: ihost uuid unique id
|
|
:param inic_dict_array: initial values for iport objects
|
|
:returns: pass or fail
|
|
"""
|
|
|
|
LOG.debug("Entering port_update_by_host %s %s" %
|
|
(host_uuid, inic_dict_array))
|
|
host_uuid.strip()
|
|
try:
|
|
ihost = self.dbapi.host_get(host_uuid)
|
|
except exception.HostNotFound:
|
|
LOG.exception("Invalid host_uuid %s" % host_uuid)
|
|
return
|
|
|
|
for inic in inic_dict_array:
|
|
LOG.info("Processing inic %s" % inic)
|
|
bootp = None
|
|
port = None
|
|
# ignore port if no MAC address present, this will
|
|
# occur for data port after they are configured via DPDK driver
|
|
if not inic['mac']:
|
|
continue
|
|
|
|
try:
|
|
inic_dict = {'host_id': ihost['id']}
|
|
inic_dict.update(inic)
|
|
if cutils.is_valid_mac(inic['mac']):
|
|
# Is this the port that the management interface is on?
|
|
if inic['mac'].strip() == ihost['mgmt_mac'].strip():
|
|
# SKIP auto create management/pxeboot network
|
|
# was for all nodes but the active controller
|
|
bootp = 'True'
|
|
inic_dict.update({'bootp': bootp})
|
|
|
|
try:
|
|
LOG.debug("Attempting to create new port %s on host %s" %
|
|
(inic_dict, ihost['id']))
|
|
|
|
port = self.dbapi.ethernet_port_get_by_mac(inic['mac'])
|
|
# update existing port with updated attributes
|
|
try:
|
|
port_dict = {
|
|
'sriov_totalvfs': inic['sriov_totalvfs'],
|
|
'sriov_numvfs': inic['sriov_numvfs'],
|
|
'sriov_vfs_pci_address':
|
|
inic['sriov_vfs_pci_address'],
|
|
'driver': inic['driver'],
|
|
'dpdksupport': inic['dpdksupport'],
|
|
'speed': inic['speed'],
|
|
}
|
|
|
|
LOG.info("port %s update attr: %s" %
|
|
(port.uuid, port_dict))
|
|
self.dbapi.ethernet_port_update(port.uuid, port_dict)
|
|
except Exception:
|
|
LOG.exception("Failed to update port %s" % inic['mac'])
|
|
pass
|
|
|
|
except Exception:
|
|
# adjust for field naming differences between the NIC
|
|
# dictionary returned by the agent and the Port model
|
|
port_dict = inic_dict.copy()
|
|
port_dict['name'] = port_dict.pop('pname', None)
|
|
port_dict['namedisplay'] = port_dict.pop('pnamedisplay',
|
|
None)
|
|
|
|
LOG.info("Attempting to create new port %s "
|
|
"on host %s" % (inic_dict, ihost.uuid))
|
|
port = self.dbapi.ethernet_port_create(
|
|
ihost.uuid, port_dict)
|
|
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(
|
|
_("Invalid host_uuid: host not found: %s") %
|
|
host_uuid)
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
if ihost.invprovision not in [k_host.PROVISIONED,
|
|
k_host.PROVISIONING]:
|
|
value = {'invprovision': k_host.UNPROVISIONED}
|
|
self.dbapi.host_update(host_uuid, value)
|
|
|
|
def lldp_tlv_dict(self, agent_neighbour_dict):
|
|
tlv_dict = {}
|
|
for k, v in agent_neighbour_dict.iteritems():
|
|
if v is not None and k in k_lldp.LLDP_TLV_VALID_LIST:
|
|
tlv_dict.update({k: v})
|
|
return tlv_dict
|
|
|
|
def lldp_agent_tlv_update(self, tlv_dict, agent):
|
|
tlv_update_list = []
|
|
tlv_create_list = []
|
|
agent_id = agent['id']
|
|
agent_uuid = agent['uuid']
|
|
|
|
tlvs = self.dbapi.lldp_tlv_get_by_agent(agent_uuid)
|
|
for k, v in tlv_dict.iteritems():
|
|
for tlv in tlvs:
|
|
if tlv['type'] == k:
|
|
tlv_value = tlv_dict.get(tlv['type'])
|
|
entry = {'type': tlv['type'],
|
|
'value': tlv_value}
|
|
if tlv['value'] != tlv_value:
|
|
tlv_update_list.append(entry)
|
|
break
|
|
else:
|
|
tlv_create_list.append({'type': k,
|
|
'value': v})
|
|
|
|
if tlv_update_list:
|
|
try:
|
|
tlvs = self.dbapi.lldp_tlv_update_bulk(tlv_update_list,
|
|
agentid=agent_id)
|
|
except Exception as e:
|
|
LOG.exception("Error during bulk TLV update for agent %s: %s",
|
|
agent_id, str(e))
|
|
raise
|
|
if tlv_create_list:
|
|
try:
|
|
self.dbapi.lldp_tlv_create_bulk(tlv_create_list,
|
|
agentid=agent_id)
|
|
except Exception as e:
|
|
LOG.exception("Error during bulk TLV create for agent %s: %s",
|
|
agent_id, str(e))
|
|
raise
|
|
|
|
def lldp_neighbour_tlv_update(self, tlv_dict, neighbour):
|
|
tlv_update_list = []
|
|
tlv_create_list = []
|
|
neighbour_id = neighbour['id']
|
|
neighbour_uuid = neighbour['uuid']
|
|
|
|
tlvs = self.dbapi.lldp_tlv_get_by_neighbour(neighbour_uuid)
|
|
for k, v in tlv_dict.iteritems():
|
|
for tlv in tlvs:
|
|
if tlv['type'] == k:
|
|
tlv_value = tlv_dict.get(tlv['type'])
|
|
entry = {'type': tlv['type'],
|
|
'value': tlv_value}
|
|
if tlv['value'] != tlv_value:
|
|
tlv_update_list.append(entry)
|
|
break
|
|
else:
|
|
tlv_create_list.append({'type': k,
|
|
'value': v})
|
|
|
|
if tlv_update_list:
|
|
try:
|
|
tlvs = self.dbapi.lldp_tlv_update_bulk(
|
|
tlv_update_list,
|
|
neighbourid=neighbour_id)
|
|
except Exception as e:
|
|
LOG.exception("Error during bulk TLV update for neighbour"
|
|
"%s: %s", neighbour_id, str(e))
|
|
raise
|
|
if tlv_create_list:
|
|
try:
|
|
self.dbapi.lldp_tlv_create_bulk(tlv_create_list,
|
|
neighbourid=neighbour_id)
|
|
except Exception as e:
|
|
LOG.exception("Error during bulk TLV create for neighbour"
|
|
"%s: %s",
|
|
neighbour_id, str(e))
|
|
raise
|
|
|
|
def lldp_agent_update_by_host(self, context,
|
|
host_uuid, agent_dict_array):
|
|
"""Create or update lldp agents for an host with the supplied data.
|
|
|
|
This method allows records for lldp agents for ihost to be created or
|
|
updated.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: host uuid unique id
|
|
:param agent_dict_array: initial values for lldp agent objects
|
|
:returns: pass or fail
|
|
"""
|
|
LOG.debug("Entering lldp_agent_update_by_host %s %s" %
|
|
(host_uuid, agent_dict_array))
|
|
host_uuid.strip()
|
|
try:
|
|
db_host = self.dbapi.host_get(host_uuid)
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(_(
|
|
"Invalid host_uuid: %s") % host_uuid)
|
|
|
|
try:
|
|
db_ports = self.dbapi.port_get_by_host(host_uuid)
|
|
except Exception:
|
|
raise exception.InventoryException(_(
|
|
"Error getting ports for host %s") % host_uuid)
|
|
|
|
try:
|
|
db_agents = self.dbapi.lldp_agent_get_by_host(host_uuid)
|
|
except Exception:
|
|
raise exception.InventoryException(_(
|
|
"Error getting LLDP agents for host %s") % host_uuid)
|
|
|
|
for agent in agent_dict_array:
|
|
port_found = None
|
|
for db_port in db_ports:
|
|
if (db_port['name'] == agent['name_or_uuid'] or
|
|
db_port['uuid'] == agent['name_or_uuid']):
|
|
port_found = db_port
|
|
break
|
|
|
|
if not port_found:
|
|
LOG.debug("Could not find port for agent %s",
|
|
agent['name_or_uuid'])
|
|
return
|
|
|
|
hostid = db_host['id']
|
|
portid = db_port['id']
|
|
|
|
agent_found = None
|
|
for db_agent in db_agents:
|
|
if db_agent['port_id'] == portid:
|
|
agent_found = db_agent
|
|
break
|
|
|
|
LOG.debug("Processing agent %s" % agent)
|
|
|
|
agent_dict = {'host_id': hostid,
|
|
'port_id': portid,
|
|
'status': agent['status']}
|
|
update_tlv = False
|
|
try:
|
|
if not agent_found:
|
|
LOG.info("Attempting to create new LLDP agent "
|
|
"%s on host %s" % (agent_dict, hostid))
|
|
if agent['state'] != k_lldp.LLDP_AGENT_STATE_REMOVED:
|
|
db_agent = self.dbapi.lldp_agent_create(portid,
|
|
hostid,
|
|
agent_dict)
|
|
update_tlv = True
|
|
else:
|
|
# If the agent exists, try to update some of the fields
|
|
# or remove it
|
|
agent_uuid = db_agent['uuid']
|
|
if agent['state'] == k_lldp.LLDP_AGENT_STATE_REMOVED:
|
|
db_agent = self.dbapi.lldp_agent_destroy(agent_uuid)
|
|
else:
|
|
attr = {'status': agent['status'],
|
|
'system_name': agent['system_name']}
|
|
db_agent = self.dbapi.lldp_agent_update(agent_uuid,
|
|
attr)
|
|
update_tlv = True
|
|
|
|
if update_tlv:
|
|
tlv_dict = self.lldp_tlv_dict(agent)
|
|
self.lldp_agent_tlv_update(tlv_dict, db_agent)
|
|
|
|
except exception.InvalidParameterValue:
|
|
raise exception.InventoryException(_(
|
|
"Failed to update/delete non-existing"
|
|
"lldp agent %s") % agent_uuid)
|
|
except exception.LLDPAgentExists:
|
|
raise exception.InventoryException(_(
|
|
"Failed to add LLDP agent %s. "
|
|
"Already exists") % agent_uuid)
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(_(
|
|
"Invalid host_uuid: host not found: %s") %
|
|
host_uuid)
|
|
except exception.PortNotFound:
|
|
raise exception.InventoryException(_(
|
|
"Invalid port id: port not found: %s") %
|
|
portid)
|
|
except Exception as e:
|
|
raise exception.InventoryException(_(
|
|
"Failed to update lldp agent: %s") % e)
|
|
|
|
def lldp_neighbour_update_by_host(self, context,
|
|
host_uuid, neighbour_dict_array):
|
|
"""Create or update lldp neighbours for an ihost with the supplied data.
|
|
|
|
This method allows records for lldp neighbours for ihost to be created
|
|
or updated.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: host uuid unique id
|
|
:param neighbour_dict_array: initial values for lldp neighbour objects
|
|
:returns: pass or fail
|
|
"""
|
|
LOG.debug("Entering lldp_neighbour_update_by_host %s %s" %
|
|
(host_uuid, neighbour_dict_array))
|
|
host_uuid.strip()
|
|
try:
|
|
db_host = self.dbapi.host_get(host_uuid)
|
|
except Exception:
|
|
raise exception.InventoryException(_(
|
|
"Invalid host_uuid: %s") % host_uuid)
|
|
|
|
try:
|
|
db_ports = self.dbapi.port_get_by_host(host_uuid)
|
|
except Exception:
|
|
raise exception.InventoryException(_(
|
|
"Error getting ports for host %s") % host_uuid)
|
|
|
|
try:
|
|
db_neighbours = self.dbapi.lldp_neighbour_get_by_host(host_uuid)
|
|
except Exception:
|
|
raise exception.InventoryException(_(
|
|
"Error getting LLDP neighbours for host %s") % host_uuid)
|
|
|
|
reported = set([(d['msap']) for d in neighbour_dict_array])
|
|
stale = [d for d in db_neighbours if (d['msap']) not in reported]
|
|
for neighbour in stale:
|
|
db_neighbour = self.dbapi.lldp_neighbour_destroy(
|
|
neighbour['uuid'])
|
|
|
|
for neighbour in neighbour_dict_array:
|
|
port_found = None
|
|
for db_port in db_ports:
|
|
if (db_port['name'] == neighbour['name_or_uuid'] or
|
|
db_port['uuid'] == neighbour['name_or_uuid']):
|
|
port_found = db_port
|
|
break
|
|
|
|
if not port_found:
|
|
LOG.debug("Could not find port for neighbour %s",
|
|
neighbour['name'])
|
|
return
|
|
|
|
LOG.debug("Processing lldp neighbour %s" % neighbour)
|
|
|
|
hostid = db_host['id']
|
|
portid = db_port['id']
|
|
msap = neighbour['msap']
|
|
state = neighbour['state']
|
|
|
|
neighbour_dict = {'host_id': hostid,
|
|
'port_id': portid,
|
|
'msap': msap}
|
|
|
|
neighbour_found = False
|
|
for db_neighbour in db_neighbours:
|
|
if db_neighbour['msap'] == msap:
|
|
neighbour_found = db_neighbour
|
|
break
|
|
|
|
update_tlv = False
|
|
try:
|
|
if not neighbour_found:
|
|
LOG.info("Attempting to create new lldp neighbour "
|
|
"%r on host %s" % (neighbour_dict, hostid))
|
|
db_neighbour = self.dbapi.lldp_neighbour_create(
|
|
portid, hostid, neighbour_dict)
|
|
update_tlv = True
|
|
else:
|
|
# If the neighbour exists, remove it if requested by
|
|
# the agent. Otherwise, trigger a TLV update. There
|
|
# are currently no neighbour attributes that need to
|
|
# be updated.
|
|
if state == k_lldp.LLDP_NEIGHBOUR_STATE_REMOVED:
|
|
db_neighbour = self.dbapi.lldp_neighbour_destroy(
|
|
db_neighbour['uuid'])
|
|
else:
|
|
update_tlv = True
|
|
if update_tlv:
|
|
tlv_dict = self.lldp_tlv_dict(neighbour)
|
|
self.lldp_neighbour_tlv_update(tlv_dict,
|
|
db_neighbour)
|
|
except exception.InvalidParameterValue:
|
|
raise exception.InventoryException(_(
|
|
"Failed to update/delete lldp neighbour. "
|
|
"Invalid parameter: %r") % tlv_dict)
|
|
except exception.LLDPNeighbourExists:
|
|
raise exception.InventoryException(_(
|
|
"Failed to add lldp neighbour %r. "
|
|
"Already exists") % neighbour_dict)
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(_(
|
|
"Invalid host_uuid: host not found: %s") %
|
|
host_uuid)
|
|
except exception.PortNotFound:
|
|
raise exception.InventoryException(
|
|
_("Invalid port id: port not found: %s") %
|
|
portid)
|
|
except Exception as e:
|
|
raise exception.InventoryException(_(
|
|
"Couldn't update LLDP neighbour: %s") % e)
|
|
|
|
def pci_device_update_by_host(self, context,
|
|
host_uuid, pci_device_dict_array):
|
|
"""Create devices for an ihost with the supplied data.
|
|
|
|
This method allows records for devices for ihost to be created.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: host uuid unique id
|
|
:param pci_device_dict_array: initial values for device objects
|
|
:returns: pass or fail
|
|
"""
|
|
LOG.debug("Entering device_update_by_host %s %s" %
|
|
(host_uuid, pci_device_dict_array))
|
|
host_uuid.strip()
|
|
try:
|
|
host = self.dbapi.host_get(host_uuid)
|
|
except exception.HostNotFound:
|
|
LOG.exception("Invalid host_uuid %s" % host_uuid)
|
|
return
|
|
for pci_dev in pci_device_dict_array:
|
|
LOG.debug("Processing dev %s" % pci_dev)
|
|
try:
|
|
pci_dev_dict = {'host_id': host['id']}
|
|
pci_dev_dict.update(pci_dev)
|
|
dev_found = None
|
|
try:
|
|
dev = self.dbapi.pci_device_get(pci_dev['pciaddr'],
|
|
hostid=host['id'])
|
|
dev_found = dev
|
|
if not dev:
|
|
LOG.info("Attempting to create new device "
|
|
"%s on host %s" % (pci_dev_dict, host['id']))
|
|
dev = self.dbapi.pci_device_create(host['id'],
|
|
pci_dev_dict)
|
|
except Exception:
|
|
LOG.info("Attempting to create new device "
|
|
"%s on host %s" % (pci_dev_dict, host['id']))
|
|
dev = self.dbapi.pci_device_create(host['id'],
|
|
pci_dev_dict)
|
|
|
|
# If the device exists, try to update some of the fields
|
|
if dev_found:
|
|
try:
|
|
attr = {
|
|
'pclass_id': pci_dev['pclass_id'],
|
|
'pvendor_id': pci_dev['pvendor_id'],
|
|
'pdevice_id': pci_dev['pdevice_id'],
|
|
'pclass': pci_dev['pclass'],
|
|
'pvendor': pci_dev['pvendor'],
|
|
'psvendor': pci_dev['psvendor'],
|
|
'psdevice': pci_dev['psdevice'],
|
|
'sriov_totalvfs': pci_dev['sriov_totalvfs'],
|
|
'sriov_numvfs': pci_dev['sriov_numvfs'],
|
|
'sriov_vfs_pci_address':
|
|
pci_dev['sriov_vfs_pci_address'],
|
|
'driver': pci_dev['driver']}
|
|
LOG.info("attr: %s" % attr)
|
|
dev = self.dbapi.pci_device_update(dev['uuid'], attr)
|
|
except Exception:
|
|
LOG.exception("Failed to update port %s" %
|
|
dev['pciaddr'])
|
|
pass
|
|
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(
|
|
_("Invalid host_uuid: host not found: %s") %
|
|
host_uuid)
|
|
except Exception:
|
|
pass
|
|
|
|
def numas_update_by_host(self, context,
|
|
host_uuid, inuma_dict_array):
|
|
"""Create inumas for an ihost with the supplied data.
|
|
|
|
This method allows records for inumas for ihost to be created.
|
|
Updates the port node_id once its available.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: ihost uuid unique id
|
|
:param inuma_dict_array: initial values for inuma objects
|
|
:returns: pass or fail
|
|
"""
|
|
|
|
host_uuid.strip()
|
|
try:
|
|
ihost = self.dbapi.host_get(host_uuid)
|
|
except exception.HostNotFound:
|
|
LOG.exception("Invalid host_uuid %s" % host_uuid)
|
|
return
|
|
|
|
try:
|
|
# Get host numa nodes which may already be in db
|
|
mynumas = self.dbapi.node_get_by_host(host_uuid)
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(_(
|
|
"Invalid host_uuid: host not found: %s") % host_uuid)
|
|
|
|
mynuma_nodes = [n.numa_node for n in mynumas]
|
|
|
|
# perform update for ports
|
|
ports = self.dbapi.ethernet_port_get_by_host(host_uuid)
|
|
for i in inuma_dict_array:
|
|
if 'numa_node' in i and i['numa_node'] in mynuma_nodes:
|
|
LOG.info("Already in db numa_node=%s mynuma_nodes=%s" %
|
|
(i['numa_node'], mynuma_nodes))
|
|
continue
|
|
|
|
try:
|
|
inuma_dict = {'host_id': ihost['id']}
|
|
|
|
inuma_dict.update(i)
|
|
|
|
inuma = self.dbapi.node_create(ihost['id'], inuma_dict)
|
|
|
|
for port in ports:
|
|
port_node = port['numa_node']
|
|
if port_node == -1:
|
|
port_node = 0 # special handling
|
|
|
|
if port_node == inuma['numa_node']:
|
|
attr = {'node_id': inuma['id']}
|
|
self.dbapi.ethernet_port_update(port['uuid'], attr)
|
|
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(
|
|
_("Invalid host_uuid: host not found: %s") %
|
|
host_uuid)
|
|
except Exception: # this info may have been posted previously
|
|
pass
|
|
|
|
def _get_default_platform_cpu_count(self, ihost, node,
|
|
cpu_count, hyperthreading):
|
|
"""Return the initial number of reserved logical cores for platform
|
|
use. This can be overridden later by the end user.
|
|
"""
|
|
cpus = 0
|
|
if cutils.host_has_function(ihost, k_host.COMPUTE) and node == 0:
|
|
cpus += 1 if not hyperthreading else 2
|
|
if cutils.host_has_function(ihost, k_host.CONTROLLER):
|
|
cpus += 1 if not hyperthreading else 2
|
|
return cpus
|
|
|
|
def _get_default_vswitch_cpu_count(self, ihost, node,
|
|
cpu_count, hyperthreading):
|
|
"""Return the initial number of reserved logical cores for vswitch use.
|
|
This can be overridden later by the end user.
|
|
"""
|
|
if cutils.host_has_function(ihost, k_host.COMPUTE) and node == 0:
|
|
physical_cores = (cpu_count / 2) if hyperthreading else cpu_count
|
|
system_mode = self.dbapi.system_get_one().system_mode
|
|
if system_mode == constants.SYSTEM_MODE_SIMPLEX:
|
|
return 1 if not hyperthreading else 2
|
|
else:
|
|
if physical_cores > 4:
|
|
return 2 if not hyperthreading else 4
|
|
elif physical_cores > 1:
|
|
return 1 if not hyperthreading else 2
|
|
return 0
|
|
|
|
def _get_default_shared_cpu_count(self, ihost, node,
|
|
cpu_count, hyperthreading):
|
|
"""Return the initial number of reserved logical cores for shared
|
|
use. This can be overridden later by the end user.
|
|
"""
|
|
return 0
|
|
|
|
def _sort_by_socket_and_coreid(self, icpu_dict):
|
|
"""Sort a list of cpu dict objects such that lower numbered sockets
|
|
appear first and that threads of the same core are adjacent in the
|
|
list with the lowest thread number appearing first.
|
|
"""
|
|
return int(icpu_dict['numa_node']), int(icpu_dict['core']), int(icpu_dict['thread']) # noqa
|
|
|
|
def _get_hyperthreading_enabled(self, cpu_list):
|
|
"""Determine if hyperthreading is enabled based on whether any threads
|
|
exist with a threadId greater than 0
|
|
"""
|
|
for cpu in cpu_list:
|
|
if int(cpu['thread']) > 0:
|
|
return True
|
|
return False
|
|
|
|
def _get_node_cpu_count(self, cpu_list, node):
|
|
count = 0
|
|
for cpu in cpu_list:
|
|
count += 1 if int(cpu['numa_node']) == node else 0
|
|
return count
|
|
|
|
def _get_default_cpu_functions(self, host, node, cpu_list, hyperthreading):
|
|
"""Return the default list of CPU functions to be reserved for this
|
|
host on the specified numa node.
|
|
"""
|
|
functions = []
|
|
cpu_count = self._get_node_cpu_count(cpu_list, node)
|
|
# Determine how many platform cpus need to be reserved
|
|
count = self._get_default_platform_cpu_count(
|
|
host, node, cpu_count, hyperthreading)
|
|
for i in range(0, count):
|
|
functions.append(constants.PLATFORM_FUNCTION)
|
|
# Determine how many vswitch cpus need to be reserved
|
|
count = self._get_default_vswitch_cpu_count(
|
|
host, node, cpu_count, hyperthreading)
|
|
for i in range(0, count):
|
|
functions.append(constants.VSWITCH_FUNCTION)
|
|
# Determine how many shared cpus need to be reserved
|
|
count = self._get_default_shared_cpu_count(
|
|
host, node, cpu_count, hyperthreading)
|
|
for i in range(0, count):
|
|
functions.append(constants.SHARED_FUNCTION)
|
|
# Assign the default function to the remaining cpus
|
|
for i in range(0, (cpu_count - len(functions))):
|
|
functions.append(cpu_utils.get_default_function(host))
|
|
return functions
|
|
|
|
def print_cpu_topology(self, hostname=None, subfunctions=None,
|
|
reference=None,
|
|
sockets=None, cores=None, threads=None):
|
|
"""Print logical cpu topology table (for debug reasons).
|
|
|
|
:param hostname: hostname
|
|
:param subfunctions: subfunctions
|
|
:param reference: reference label
|
|
:param sockets: dictionary of socket_ids, sockets[cpu_id]
|
|
:param cores: dictionary of core_ids, cores[cpu_id]
|
|
:param threads: dictionary of thread_ids, threads[cpu_id]
|
|
:returns: None
|
|
"""
|
|
if sockets is None or cores is None or threads is None:
|
|
LOG.error("print_cpu_topology: topology not defined. "
|
|
"sockets=%s, cores=%s, threads=%s"
|
|
% (sockets, cores, threads))
|
|
return
|
|
|
|
# calculate overall cpu topology stats
|
|
n_sockets = len(set(sockets.values()))
|
|
n_cores = len(set(cores.values()))
|
|
n_threads = len(set(threads.values()))
|
|
if n_sockets < 1 or n_cores < 1 or n_threads < 1:
|
|
LOG.error("print_cpu_topology: unexpected topology. "
|
|
"n_sockets=%d, n_cores=%d, n_threads=%d"
|
|
% (n_sockets, n_cores, n_threads))
|
|
return
|
|
|
|
# build each line of output
|
|
ll = ''
|
|
s = ''
|
|
c = ''
|
|
t = ''
|
|
for cpu in sorted(cores.keys()):
|
|
ll += '%3d' % cpu
|
|
s += '%3d' % sockets[cpu]
|
|
c += '%3d' % cores[cpu]
|
|
t += '%3d' % threads[cpu]
|
|
|
|
LOG.info('Logical CPU topology: host:%s (%s), '
|
|
'sockets:%d, cores/socket=%d, threads/core=%d, reference:%s'
|
|
% (hostname, subfunctions, n_sockets, n_cores, n_threads,
|
|
reference))
|
|
LOG.info('%9s : %s' % ('cpu_id', ll))
|
|
LOG.info('%9s : %s' % ('socket_id', s))
|
|
LOG.info('%9s : %s' % ('core_id', c))
|
|
LOG.info('%9s : %s' % ('thread_id', t))
|
|
|
|
def update_cpu_config(self, context, host_uuid):
|
|
LOG.info("TODO send to systemconfig update_cpu_config")
|
|
|
|
def cpus_update_by_host(self, context,
|
|
host_uuid, icpu_dict_array,
|
|
force_grub_update=False):
|
|
"""Create cpus for an ihost with the supplied data.
|
|
|
|
This method allows records for cpus for ihost to be created.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: ihost uuid unique id
|
|
:param icpu_dict_array: initial values for cpu objects
|
|
:param force_grub_update: bool value to force grub update
|
|
:returns: pass or fail
|
|
"""
|
|
|
|
host_uuid.strip()
|
|
try:
|
|
ihost = self.dbapi.host_get(host_uuid)
|
|
except exception.HostNotFound:
|
|
LOG.exception("Invalid host_uuid %s" % host_uuid)
|
|
return
|
|
|
|
host_id = ihost['id']
|
|
ihost_inodes = self.dbapi.node_get_by_host(host_uuid)
|
|
|
|
icpus = self.dbapi.cpu_get_by_host(host_uuid)
|
|
|
|
num_cpus_dict = len(icpu_dict_array)
|
|
num_cpus_db = len(icpus)
|
|
|
|
# Capture 'current' topology in dictionary format
|
|
cs = {}
|
|
cc = {}
|
|
ct = {}
|
|
if num_cpus_dict > 0:
|
|
for icpu in icpu_dict_array:
|
|
cpu_id = icpu.get('cpu')
|
|
cs[cpu_id] = icpu.get('numa_node')
|
|
cc[cpu_id] = icpu.get('core')
|
|
ct[cpu_id] = icpu.get('thread')
|
|
|
|
# Capture 'previous' topology in dictionary format
|
|
ps = {}
|
|
pc = {}
|
|
pt = {}
|
|
if num_cpus_db > 0:
|
|
for icpu in icpus:
|
|
cpu_id = icpu.get('cpu')
|
|
core_id = icpu.get('core')
|
|
thread_id = icpu.get('thread')
|
|
node_id = icpu.get('node_id')
|
|
socket_id = None
|
|
for inode in ihost_inodes:
|
|
if node_id == inode.get('id'):
|
|
socket_id = inode.get('numa_node')
|
|
break
|
|
ps[cpu_id] = socket_id
|
|
pc[cpu_id] = core_id
|
|
pt[cpu_id] = thread_id
|
|
|
|
if num_cpus_dict > 0 and num_cpus_db == 0:
|
|
self.print_cpu_topology(hostname=ihost.get('hostname'),
|
|
subfunctions=ihost.get('subfunctions'),
|
|
reference='current (initial)',
|
|
sockets=cs, cores=cc, threads=ct)
|
|
|
|
if num_cpus_dict > 0 and num_cpus_db > 0:
|
|
LOG.debug("num_cpus_dict=%d num_cpus_db= %d. "
|
|
"icpud_dict_array= %s cpus.as_dict= %s" %
|
|
(num_cpus_dict, num_cpus_db, icpu_dict_array, icpus))
|
|
|
|
# Skip update if topology has not changed
|
|
if ps == cs and pc == cc and pt == ct:
|
|
self.print_cpu_topology(hostname=ihost.get('hostname'),
|
|
subfunctions=ihost.get('subfunctions'),
|
|
reference='current (unchanged)',
|
|
sockets=cs, cores=cc, threads=ct)
|
|
if ihost.administrative == k_host.ADMIN_LOCKED and \
|
|
force_grub_update:
|
|
self.update_cpu_config(context, host_uuid)
|
|
return
|
|
|
|
self.print_cpu_topology(hostname=ihost.get('hostname'),
|
|
subfunctions=ihost.get('subfunctions'),
|
|
reference='previous',
|
|
sockets=ps, cores=pc, threads=pt)
|
|
self.print_cpu_topology(hostname=ihost.get('hostname'),
|
|
subfunctions=ihost.get('subfunctions'),
|
|
reference='current (CHANGED)',
|
|
sockets=cs, cores=cc, threads=ct)
|
|
|
|
# there has been an update. Delete db entries and replace.
|
|
for icpu in icpus:
|
|
self.dbapi.cpu_destroy(icpu.uuid)
|
|
|
|
# sort the list of cpus by socket and coreid
|
|
cpu_list = sorted(icpu_dict_array, key=self._sort_by_socket_and_coreid)
|
|
|
|
# determine if hyperthreading is enabled
|
|
hyperthreading = self._get_hyperthreading_enabled(cpu_list)
|
|
|
|
# build the list of functions to be assigned to each cpu
|
|
functions = {}
|
|
for n in ihost_inodes:
|
|
numa_node = int(n.numa_node)
|
|
functions[numa_node] = self._get_default_cpu_functions(
|
|
ihost, numa_node, cpu_list, hyperthreading)
|
|
|
|
for data in cpu_list:
|
|
try:
|
|
node_id = None
|
|
for n in ihost_inodes:
|
|
numa_node = int(n.numa_node)
|
|
if numa_node == int(data['numa_node']):
|
|
node_id = n['id']
|
|
break
|
|
|
|
cpu_dict = {'host_id': host_id,
|
|
'node_id': node_id,
|
|
'allocated_function': functions[numa_node].pop(0)}
|
|
|
|
cpu_dict.update(data)
|
|
|
|
self.dbapi.cpu_create(host_id, cpu_dict)
|
|
|
|
except exception.HostNotFound:
|
|
raise exception.InventoryException(
|
|
_("Invalid host_uuid: host not found: %s") %
|
|
host_uuid)
|
|
except Exception:
|
|
# info may have already been posted
|
|
pass
|
|
|
|
# if it is the first controller wait for the initial config to
|
|
# be completed
|
|
if ((utils.is_host_simplex_controller(ihost) and
|
|
os.path.isfile(tsc.INITIAL_CONFIG_COMPLETE_FLAG)) or
|
|
(not utils.is_host_simplex_controller(ihost) and
|
|
ihost.administrative == k_host.ADMIN_LOCKED)):
|
|
LOG.info("Update CPU grub config, host_uuid (%s), name (%s)"
|
|
% (host_uuid, ihost.get('hostname')))
|
|
self.update_cpu_config(context, host_uuid)
|
|
|
|
return
|
|
|
|
def _get_platform_reserved_memory(self, ihost, node):
|
|
low_core = cutils.is_low_core_system(ihost, self.dbapi)
|
|
reserved = cutils.get_required_platform_reserved_memory(
|
|
ihost, node, low_core)
|
|
return {'platform_reserved_mib': reserved} if reserved else {}
|
|
|
|
def memory_update_by_host(self, context,
|
|
host_uuid, imemory_dict_array,
|
|
force_update):
|
|
"""Create or update memory for a host with the supplied data.
|
|
|
|
This method allows records for memory for host to be created,
|
|
or updated.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: ihost uuid unique id
|
|
:param imemory_dict_array: initial values for cpu objects
|
|
:param force_update: force host memory update
|
|
:returns: pass or fail
|
|
"""
|
|
|
|
host_uuid.strip()
|
|
try:
|
|
ihost = self.dbapi.host_get(host_uuid)
|
|
except exception.ServerNotFound:
|
|
LOG.exception("Invalid host_uuid %s" % host_uuid)
|
|
return
|
|
|
|
if ihost['administrative'] == k_host.ADMIN_LOCKED and \
|
|
ihost['invprovision'] == k_host.PROVISIONED and \
|
|
not force_update:
|
|
LOG.debug("Ignore the host memory audit after the host is locked")
|
|
return
|
|
|
|
forihostid = ihost['id']
|
|
ihost_inodes = self.dbapi.node_get_by_host(host_uuid)
|
|
|
|
for i in imemory_dict_array:
|
|
forinodeid = None
|
|
inode_uuid = None
|
|
for n in ihost_inodes:
|
|
numa_node = int(n.numa_node)
|
|
if numa_node == int(i['numa_node']):
|
|
forinodeid = n['id']
|
|
inode_uuid = n['uuid']
|
|
inode_uuid.strip()
|
|
break
|
|
else:
|
|
# not found in host_nodes, do not add memory element
|
|
continue
|
|
|
|
mem_dict = {'forihostid': forihostid,
|
|
'forinodeid': forinodeid}
|
|
|
|
mem_dict.update(i)
|
|
|
|
# Do not allow updates to the amounts of reserved memory.
|
|
mem_dict.pop('platform_reserved_mib', None)
|
|
|
|
# numa_node is not stored against imemory table
|
|
mem_dict.pop('numa_node', None)
|
|
|
|
# clear the pending hugepage number for unlocked nodes
|
|
if ihost.administrative == k_host.ADMIN_UNLOCKED:
|
|
mem_dict['vm_hugepages_nr_2M_pending'] = None
|
|
mem_dict['vm_hugepages_nr_1G_pending'] = None
|
|
|
|
try:
|
|
imems = self.dbapi.memory_get_by_host_node(host_uuid,
|
|
inode_uuid)
|
|
if not imems:
|
|
# Set the amount of memory reserved for platform use.
|
|
mem_dict.update(self._get_platform_reserved_memory(
|
|
ihost, i['numa_node']))
|
|
self.dbapi.memory_create(forihostid, mem_dict)
|
|
else:
|
|
for imem in imems:
|
|
# Include 4K pages in the displayed VM memtotal
|
|
if imem.vm_hugepages_nr_4K is not None:
|
|
vm_4K_mib = \
|
|
(imem.vm_hugepages_nr_4K /
|
|
constants.NUM_4K_PER_MiB)
|
|
mem_dict['memtotal_mib'] += vm_4K_mib
|
|
mem_dict['memavail_mib'] += vm_4K_mib
|
|
self.dbapi.memory_update(imem['uuid'],
|
|
mem_dict)
|
|
except Exception:
|
|
# Set the amount of memory reserved for platform use.
|
|
mem_dict.update(self._get_platform_reserved_memory(
|
|
ihost, i['numa_node']))
|
|
self.dbapi.memory_create(forihostid, mem_dict)
|
|
pass
|
|
|
|
return
|
|
|
|
def _get_disk_available_mib(self, disk, agent_disk_dict):
|
|
partitions = self.dbapi.partition_get_by_idisk(disk['uuid'])
|
|
|
|
if not partitions:
|
|
LOG.debug("Disk %s has no partitions" % disk.uuid)
|
|
return agent_disk_dict['available_mib']
|
|
|
|
available_mib = agent_disk_dict['available_mib']
|
|
for part in partitions:
|
|
if (part.status in
|
|
[constants.PARTITION_CREATE_IN_SVC_STATUS,
|
|
constants.PARTITION_CREATE_ON_UNLOCK_STATUS]):
|
|
available_mib = available_mib - part.size_mib
|
|
|
|
LOG.debug("Disk available mib host - %s disk - %s av - %s" %
|
|
(disk.host_id, disk.device_node, available_mib))
|
|
return available_mib
|
|
|
|
def platform_update_by_host(self, context,
|
|
host_uuid, imsg_dict):
|
|
"""Create or update imemory for an ihost with the supplied data.
|
|
|
|
This method allows records for memory for ihost to be created,
|
|
or updated.
|
|
|
|
This method is invoked on initialization once. Note, swact also
|
|
results in restart, but not of inventory-agent?
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: ihost uuid unique id
|
|
:param imsg_dict: inventory message
|
|
:returns: pass or fail
|
|
"""
|
|
|
|
host_uuid.strip()
|
|
try:
|
|
ihost = self.dbapi.host_get(host_uuid)
|
|
except exception.HostNotFound:
|
|
LOG.exception("Invalid host_uuid %s" % host_uuid)
|
|
return
|
|
|
|
availability = imsg_dict.get('availability')
|
|
|
|
val = {}
|
|
action_state = imsg_dict.get(k_host.HOST_ACTION_STATE)
|
|
if action_state and action_state != ihost.action_state:
|
|
LOG.info("%s updating action_state=%s" % (ihost.hostname,
|
|
action_state))
|
|
val[k_host.HOST_ACTION_STATE] = action_state
|
|
|
|
iscsi_initiator_name = imsg_dict.get('iscsi_initiator_name')
|
|
if (iscsi_initiator_name and
|
|
ihost.iscsi_initiator_name is None):
|
|
LOG.info("%s updating iscsi initiator=%s" %
|
|
(ihost.hostname, iscsi_initiator_name))
|
|
val['iscsi_initiator_name'] = iscsi_initiator_name
|
|
|
|
if val:
|
|
ihost = self.dbapi.host_update(host_uuid, val)
|
|
|
|
if not availability:
|
|
return
|
|
|
|
if cutils.host_has_function(ihost, k_host.COMPUTE):
|
|
if availability == k_host.VIM_SERVICES_ENABLED:
|
|
# TODO(sc) report to systemconfig platform available, it will
|
|
# independently also update with config applied
|
|
LOG.info("Need report to systemconfig iplatform available "
|
|
"for ihost=%s imsg=%s"
|
|
% (host_uuid, imsg_dict))
|
|
elif availability == k_host.AVAILABILITY_OFFLINE:
|
|
# TODO(sc) report to systemconfig platform AVAILABILITY_OFFLINE
|
|
LOG.info("Need report iplatform not available for host=%s "
|
|
"imsg= %s" % (host_uuid, imsg_dict))
|
|
|
|
if ((ihost.personality == k_host.STORAGE and
|
|
ihost.hostname == k_host.STORAGE_0_HOSTNAME) or
|
|
(ihost.personality == k_host.CONTROLLER)):
|
|
# TODO(sc) report to systemconfig platform available
|
|
LOG.info("TODO report to systemconfig platform available")
|
|
|
|
def subfunctions_update_by_host(self, context,
|
|
host_uuid, subfunctions):
|
|
"""Update subfunctions for a host.
|
|
|
|
This method allows records for subfunctions to be updated.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: ihost uuid unique id
|
|
:param subfunctions: subfunctions provided by the ihost
|
|
:returns: pass or fail
|
|
"""
|
|
host_uuid.strip()
|
|
|
|
# Create the host entry in neutron to allow for data interfaces to
|
|
# be configured on a combined node
|
|
if (k_host.CONTROLLER in subfunctions and
|
|
k_host.COMPUTE in subfunctions):
|
|
try:
|
|
ihost = self.dbapi.host_get(host_uuid)
|
|
except exception.HostNotFound:
|
|
LOG.exception("Invalid host_uuid %s" % host_uuid)
|
|
return
|
|
|
|
try:
|
|
neutron_host_id = \
|
|
self._openstack.get_neutron_host_id_by_name(
|
|
context, ihost['hostname'])
|
|
if not neutron_host_id:
|
|
self._openstack.create_neutron_host(context,
|
|
host_uuid,
|
|
ihost['hostname'])
|
|
elif neutron_host_id != host_uuid:
|
|
self._openstack.delete_neutron_host(context,
|
|
neutron_host_id)
|
|
self._openstack.create_neutron_host(context,
|
|
host_uuid,
|
|
ihost['hostname'])
|
|
except Exception: # TODO(sc) Needs better exception
|
|
LOG.exception("Failed in neutron stuff")
|
|
|
|
ihost_val = {'subfunctions': subfunctions}
|
|
self.dbapi.host_update(host_uuid, ihost_val)
|
|
|
|
def get_host_by_macs(self, context, host_macs):
|
|
"""Finds ihost db entry based upon the mac list
|
|
|
|
This method returns an ihost if it matches a mac
|
|
|
|
:param context: an admin context
|
|
:param host_macs: list of mac addresses
|
|
:returns: ihost object, including all fields.
|
|
"""
|
|
|
|
ihosts = objects.Host.list(context)
|
|
|
|
LOG.debug("Checking ihost db for macs: %s" % host_macs)
|
|
for mac in host_macs:
|
|
try:
|
|
mac = mac.rstrip()
|
|
mac = cutils.validate_and_normalize_mac(mac)
|
|
except Exception:
|
|
LOG.warn("get_host_by_macs invalid mac: %s" % mac)
|
|
continue
|
|
|
|
for host in ihosts:
|
|
if host.mgmt_mac == mac:
|
|
LOG.info("Host found ihost db for macs: %s" %
|
|
host.hostname)
|
|
return host
|
|
LOG.debug("RPC get_host_by_macs called but found no ihost.")
|
|
|
|
def get_host_by_hostname(self, context, hostname):
|
|
"""Finds host db entry based upon the host hostname
|
|
|
|
This method returns a host if it matches the host
|
|
hostname.
|
|
|
|
:param context: an admin context
|
|
:param hostname: host hostname
|
|
:returns: host object, including all fields.
|
|
"""
|
|
|
|
try:
|
|
return objects.Host.get_by_filters_one(context,
|
|
{'hostname': hostname})
|
|
except exception.HostNotFound:
|
|
pass
|
|
|
|
LOG.info("RPC host_get_by_hostname called but found no host.")
|
|
|
|
def _audit_host_action(self, host):
|
|
"""Audit whether the host_action needs to be terminated or escalated.
|
|
"""
|
|
|
|
if host.administrative == k_host.ADMIN_UNLOCKED:
|
|
host_action_str = host.host_action or ""
|
|
|
|
if (host_action_str.startswith(k_host.ACTION_FORCE_LOCK) or
|
|
host_action_str.startswith(k_host.ACTION_LOCK)):
|
|
|
|
task_str = host.task or ""
|
|
if (('--' in host_action_str and
|
|
host_action_str.startswith(
|
|
k_host.ACTION_FORCE_LOCK)) or
|
|
('----------' in host_action_str and
|
|
host_action_str.startswith(k_host.ACTION_LOCK))):
|
|
ihost_mtc = host.as_dict()
|
|
keepkeys = ['host_action', 'vim_progress_status']
|
|
ihost_mtc = cutils.removekeys_nonmtce(ihost_mtc,
|
|
keepkeys)
|
|
|
|
if host_action_str.startswith(
|
|
k_host.ACTION_FORCE_LOCK):
|
|
timeout_in_secs = 6
|
|
ihost_mtc['operation'] = 'modify'
|
|
ihost_mtc['action'] = k_host.ACTION_FORCE_LOCK
|
|
ihost_mtc['task'] = k_host.FORCE_LOCKING
|
|
LOG.warn("host_action override %s" %
|
|
ihost_mtc)
|
|
mtce_api.host_modify(
|
|
self._api_token, self._mtc_address, self._mtc_port,
|
|
ihost_mtc, timeout_in_secs)
|
|
|
|
# need time for FORCE_LOCK mtce to clear
|
|
if '----' in host_action_str:
|
|
host_action_str = ""
|
|
else:
|
|
host_action_str += "-"
|
|
|
|
if (task_str.startswith(k_host.FORCE_LOCKING) or
|
|
task_str.startswith(k_host.LOCKING)):
|
|
val = {'task': "",
|
|
'host_action': host_action_str,
|
|
'vim_progress_status': ""}
|
|
else:
|
|
val = {'host_action': host_action_str,
|
|
'vim_progress_status': ""}
|
|
else:
|
|
host_action_str += "-"
|
|
if (task_str.startswith(k_host.FORCE_LOCKING) or
|
|
task_str.startswith(k_host.LOCKING)):
|
|
task_str += "-"
|
|
val = {'task': task_str,
|
|
'host_action': host_action_str}
|
|
else:
|
|
val = {'host_action': host_action_str}
|
|
|
|
self.dbapi.host_update(host.uuid, val)
|
|
else: # Administrative locked already
|
|
task_str = host.task or ""
|
|
if (task_str.startswith(k_host.FORCE_LOCKING) or
|
|
task_str.startswith(k_host.LOCKING)):
|
|
val = {'task': ""}
|
|
self.dbapi.host_update(host.uuid, val)
|
|
|
|
vim_progress_status_str = host.get('vim_progress_status') or ""
|
|
if (vim_progress_status_str and
|
|
(vim_progress_status_str != k_host.VIM_SERVICES_ENABLED) and
|
|
(vim_progress_status_str != k_host.VIM_SERVICES_DISABLED)):
|
|
if '..' in vim_progress_status_str:
|
|
LOG.info("Audit clearing vim_progress_status=%s" %
|
|
vim_progress_status_str)
|
|
vim_progress_status_str = ""
|
|
else:
|
|
vim_progress_status_str += ".."
|
|
|
|
val = {'vim_progress_status': vim_progress_status_str}
|
|
self.dbapi.host_update(host.uuid, val)
|
|
|
|
def _audit_install_states(self, host):
|
|
# A node could shutdown during it's installation and the install_state
|
|
# for example could get stuck at the value "installing". To avoid
|
|
# this situation we audit the sanity of the states by appending the
|
|
# character '+' to the states in the database. After 15 minutes of the
|
|
# states not changing, set the install_state to failed.
|
|
|
|
# The audit's interval is 60sec
|
|
MAX_COUNT = 15
|
|
|
|
# Allow longer duration for booting phase
|
|
MAX_COUNT_BOOTING = 40
|
|
|
|
LOG.info("Auditing %s, install_state is %s",
|
|
host.hostname, host.install_state)
|
|
LOG.debug("Auditing %s, availability is %s",
|
|
host.hostname, host.availability)
|
|
|
|
if (host.administrative == k_host.ADMIN_LOCKED and
|
|
host.install_state is not None):
|
|
|
|
install_state = host.install_state.rstrip('+')
|
|
|
|
if host.install_state != constants.INSTALL_STATE_FAILED:
|
|
if (install_state == constants.INSTALL_STATE_BOOTING and
|
|
host.availability !=
|
|
k_host.AVAILABILITY_OFFLINE):
|
|
host.install_state = constants.INSTALL_STATE_COMPLETED
|
|
|
|
if (install_state != constants.INSTALL_STATE_INSTALLED and
|
|
install_state !=
|
|
constants.INSTALL_STATE_COMPLETED):
|
|
if (install_state ==
|
|
constants.INSTALL_STATE_INSTALLING and
|
|
host.install_state_info is not None):
|
|
if host.install_state_info.count('+') >= MAX_COUNT:
|
|
LOG.info(
|
|
"Auditing %s, install_state changed from "
|
|
"'%s' to '%s'", host.hostname,
|
|
host.install_state,
|
|
constants.INSTALL_STATE_FAILED)
|
|
host.install_state = \
|
|
constants.INSTALL_STATE_FAILED
|
|
else:
|
|
host.install_state_info += "+"
|
|
else:
|
|
if (install_state ==
|
|
constants.INSTALL_STATE_BOOTING):
|
|
max_count = MAX_COUNT_BOOTING
|
|
else:
|
|
max_count = MAX_COUNT
|
|
if host.install_state.count('+') >= max_count:
|
|
LOG.info(
|
|
"Auditing %s, install_state changed from "
|
|
"'%s' to '%s'", host.hostname,
|
|
host.install_state,
|
|
constants.INSTALL_STATE_FAILED)
|
|
host.install_state = \
|
|
constants.INSTALL_STATE_FAILED
|
|
else:
|
|
host.install_state += "+"
|
|
|
|
# It is possible we get stuck in an installed failed state. For
|
|
# example if a node gets powered down during an install booting
|
|
# state and then powered on again. Clear it if the node is
|
|
# online.
|
|
elif (host.availability == k_host.AVAILABILITY_ONLINE and
|
|
host.install_state == constants.INSTALL_STATE_FAILED):
|
|
host.install_state = constants.INSTALL_STATE_COMPLETED
|
|
|
|
self.dbapi.host_update(host.uuid,
|
|
{'install_state': host.install_state,
|
|
'install_state_info':
|
|
host.install_state_info})
|
|
|
|
def configure_systemname(self, context, systemname):
|
|
"""Configure the systemname with the supplied data.
|
|
|
|
:param context: an admin context.
|
|
:param systemname: the systemname
|
|
"""
|
|
|
|
LOG.debug("configure_systemname: sending systemname to agent(s)")
|
|
rpcapi = agent_rpcapi.AgentAPI()
|
|
rpcapi.configure_systemname(context, systemname=systemname)
|
|
|
|
return
|
|
|
|
@staticmethod
|
|
def _get_fm_entity_instance_id(host_obj):
|
|
"""
|
|
Create 'entity_instance_id' from host_obj data
|
|
"""
|
|
|
|
entity_instance_id = "%s=%s" % (fm_constants.FM_ENTITY_TYPE_HOST,
|
|
host_obj.hostname)
|
|
return entity_instance_id
|
|
|
|
def _log_host_create(self, host, reason=None):
|
|
"""
|
|
Create host discovery event customer log.
|
|
"""
|
|
if host.hostname:
|
|
hostid = host.hostname
|
|
else:
|
|
hostid = host.mgmt_mac
|
|
|
|
if reason is not None:
|
|
reason_text = ("%s has been 'discovered' on the network. (%s)" %
|
|
(hostid, reason))
|
|
else:
|
|
reason_text = ("%s has been 'discovered'." % hostid)
|
|
|
|
# action event -> FM_ALARM_TYPE_4 = 'equipment'
|
|
# FM_ALARM_SEVERITY_CLEAR to be consistent with 200.x series Info
|
|
log_data = {'hostid': hostid,
|
|
'event_id': fm_constants.FM_LOG_ID_HOST_DISCOVERED,
|
|
'entity_type': fm_constants.FM_ENTITY_TYPE_HOST,
|
|
'entity': 'host=%s.event=discovered' % hostid,
|
|
'fm_severity': fm_constants.FM_ALARM_SEVERITY_CLEAR,
|
|
'fm_event_type': fm_constants.FM_ALARM_TYPE_4,
|
|
'reason_text': reason_text,
|
|
}
|
|
self.fm_log.customer_log(log_data)
|
|
|
|
def _update_subfunctions(self, context, ihost_obj):
|
|
"""Update subfunctions."""
|
|
|
|
ihost_obj.invprovision = k_host.PROVISIONED
|
|
ihost_obj.save(context)
|
|
|
|
def notify_subfunctions_config(self, context,
|
|
host_uuid, ihost_notify_dict):
|
|
"""
|
|
Notify inventory of host subfunctions configuration status
|
|
"""
|
|
|
|
subfunctions_configured = ihost_notify_dict.get(
|
|
'subfunctions_configured') or ""
|
|
try:
|
|
ihost_obj = self.dbapi.host_get(host_uuid)
|
|
except Exception as e:
|
|
LOG.exception("notify_subfunctions_config e=%s "
|
|
"ihost=%s subfunctions=%s" %
|
|
(e, host_uuid, subfunctions_configured))
|
|
return False
|
|
|
|
if not subfunctions_configured:
|
|
self._update_subfunctions(context, ihost_obj)
|
|
|
|
def _add_port_to_list(self, interface_id, networktype, port_list):
|
|
info = {}
|
|
ports = self.dbapi.port_get_all(interfaceid=interface_id)
|
|
if ports:
|
|
info['name'] = ports[0]['name']
|
|
info['numa_node'] = ports[0]['numa_node']
|
|
info['networktype'] = networktype
|
|
if info not in port_list:
|
|
port_list.append(info)
|
|
return port_list
|
|
|
|
def bm_deprovision_by_host(self, context, host_uuid, ibm_msg_dict):
|
|
"""Update ihost upon notification of board management controller
|
|
deprovisioning.
|
|
|
|
This method also allows a dictionary of values to be passed in to
|
|
affort additional controls, if and as needed.
|
|
|
|
:param context: an admin context
|
|
:param host_uuid: ihost uuid unique id
|
|
:param ibm_msg_dict: values for additional controls or changes
|
|
:returns: pass or fail
|
|
"""
|
|
LOG.info("bm_deprovision_by_host=%s msg=%s" %
|
|
(host_uuid, ibm_msg_dict))
|
|
|
|
isensorgroups = self.dbapi.sensorgroup_get_by_host(host_uuid)
|
|
|
|
for isensorgroup in isensorgroups:
|
|
isensors = self.dbapi.sensor_get_by_sensorgroup(isensorgroup.uuid)
|
|
for isensor in isensors:
|
|
self.dbapi.sensor_destroy(isensor.uuid)
|
|
|
|
self.dbapi.sensorgroup_destroy(isensorgroup.uuid)
|
|
|
|
isensors = self.dbapi.sensor_get_by_host(host_uuid)
|
|
if isensors:
|
|
LOG.info("bm_deprovision_by_host=%s Non-group sensors=%s" %
|
|
(host_uuid, isensors))
|
|
for isensor in isensors:
|
|
self.dbapi.sensor_destroy(isensor.uuid)
|
|
|
|
isensors = self.dbapi.sensor_get_by_host(host_uuid)
|
|
|
|
return True
|
|
|
|
def configure_ttys_dcd(self, context, uuid, ttys_dcd):
|
|
"""Notify agent to configure the dcd with the supplied data.
|
|
|
|
:param context: an admin context.
|
|
:param uuid: the host uuid
|
|
:param ttys_dcd: the flag to enable/disable dcd
|
|
"""
|
|
|
|
LOG.debug("ConductorManager.configure_ttys_dcd: sending dcd update %s "
|
|
"%s to agents" % (ttys_dcd, uuid))
|
|
rpcapi = agent_rpcapi.AgentAPI()
|
|
rpcapi.configure_ttys_dcd(context, uuid=uuid, ttys_dcd=ttys_dcd)
|
|
|
|
def get_host_ttys_dcd(self, context, ihost_id):
|
|
"""
|
|
Retrieve the serial line carrier detect state for a given host
|
|
"""
|
|
ihost = self.dbapi.host_get(ihost_id)
|
|
if ihost:
|
|
return ihost.ttys_dcd
|
|
else:
|
|
LOG.error("Host: %s not found in database" % ihost_id)
|
|
return None
|
|
|
|
def _get_cinder_address_name(self, network_type):
|
|
ADDRESS_FORMAT_ARGS = (k_host.CONTROLLER_HOSTNAME,
|
|
network_type)
|
|
return "%s-cinder-%s" % ADDRESS_FORMAT_ARGS
|
|
|
|
def create_barbican_secret(self, context, name, payload):
|
|
"""Calls Barbican API to create a secret
|
|
|
|
:param context: request context.
|
|
:param name: secret name
|
|
:param payload: secret payload
|
|
"""
|
|
self._openstack.create_barbican_secret(context=context,
|
|
name=name, payload=payload)
|
|
|
|
def delete_barbican_secret(self, context, name):
|
|
"""Calls Barbican API to delete a secret
|
|
|
|
:param context: request context.
|
|
:param name: secret name
|
|
"""
|
|
self._openstack.delete_barbican_secret(context=context, name=name)
|