deb-python-os-cloud-config/os_cloud_config/nodes.py

391 lines
15 KiB
Python

# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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 logging
import time
from ironicclient import exc as ironicexp
import six
from os_cloud_config.cmd.utils import _clients as clients
from os_cloud_config import glance
LOG = logging.getLogger(__name__)
# This module is no longer used by TripleO! If you feel like changing one of
# the functions below or adding a new one, please apply your change to
# tripleo_common.utils.nodes in the tripleo-common repo.
def _ipmi_driver_info(node):
driver_info = {"ipmi_address": node["pm_addr"],
"ipmi_username": node["pm_user"],
"ipmi_password": node["pm_password"]}
for params in ('ipmi_bridging', 'ipmi_transit_address',
'ipmi_transit_channel', 'ipmi_target_address',
'ipmi_target_channel', 'ipmi_local_address'):
if node.get(params):
driver_info[params] = node[params]
return driver_info
def _pxe_drac_driver_info(node):
driver_info = {"drac_host": node["pm_addr"],
"drac_username": node["pm_user"],
"drac_password": node["pm_password"]}
return driver_info
def _pxe_ssh_driver_info(node):
if "pm_virt_type" not in node:
node["pm_virt_type"] = "virsh"
driver_info = {"ssh_address": node["pm_addr"],
"ssh_username": node["pm_user"],
"ssh_key_contents": node["pm_password"],
"ssh_virt_type": node["pm_virt_type"]}
return driver_info
def _pxe_ilo_driver_info(node):
driver_info = {"ilo_address": node["pm_addr"],
"ilo_username": node["pm_user"],
"ilo_password": node["pm_password"]}
return driver_info
def _pxe_iboot_driver_info(node):
driver_info = {"iboot_address": node["pm_addr"],
"iboot_username": node["pm_user"],
"iboot_password": node["pm_password"]}
# iboot_relay_id and iboot_port are optional
if "pm_relay_id" in node:
driver_info["iboot_relay_id"] = node["pm_relay_id"]
if "pm_port" in node:
driver_info["iboot_port"] = node["pm_port"]
return driver_info
def _fake_pxe_driver_info(node):
driver_info = {}
# The fake_pxe driver doesn't need any credentials since there's
# no power management
return driver_info
def _pxe_ucs_driver_info(node):
driver_info = {"ucs_hostname": node["pm_addr"],
"ucs_username": node["pm_user"],
"ucs_password": node["pm_password"],
"ucs_service_profile": node["pm_service_profile"]}
return driver_info
def _common_irmc_driver_info(node):
driver_info = {"irmc_address": node["pm_addr"],
"irmc_username": node["pm_user"],
"irmc_password": node["pm_password"]}
# irmc_port, irmc_auth_method, irmc_client_timeout, and
# irmc_sensor_method are optional
if "pm_port" in node:
driver_info["irmc_port"] = node["pm_port"]
if "pm_auth_method" in node:
driver_info["irmc_auth_method"] = node["pm_auth_method"]
if "pm_client_timeout" in node:
driver_info["irmc_client_timeout"] = node["pm_client_timeout"]
if "pm_sensor_method" in node:
driver_info["irmc_sensor_method"] = node["pm_sensor_method"]
return driver_info
def _pxe_irmc_driver_info(node):
return _common_irmc_driver_info(node)
def _iscsi_irmc_driver_info(node):
driver_info = _common_irmc_driver_info(node)
# irmc_deploy_iso is also required for iscsi_irmc
driver_info["irmc_deploy_iso"] = node["pm_deploy_iso"]
return driver_info
def _pxe_wol_driver_info(node):
driver_info = {"wol_host": node["pm_addr"]}
if "pm_port" in node:
driver_info["wol_port"] = node["pm_port"]
return driver_info
def _associate_deploy_kr_info(driver_info, node):
if "pxe" in node["pm_type"]:
if "kernel_id" in node:
driver_info["deploy_kernel"] = node["kernel_id"]
if "ramdisk_id" in node:
driver_info["deploy_ramdisk"] = node["ramdisk_id"]
return driver_info
def _extract_driver_info(node):
driver_info = {}
driver_info_map = {"pxe_drac": _pxe_drac_driver_info,
"pxe_ssh": _pxe_ssh_driver_info,
"pxe_ilo": _pxe_ilo_driver_info,
"pxe_iboot": _pxe_iboot_driver_info,
"fake_pxe": _fake_pxe_driver_info,
"pxe_ucs": _pxe_ucs_driver_info,
"pxe_irmc": _pxe_irmc_driver_info,
"iscsi_irmc": _iscsi_irmc_driver_info,
# agent_irmc and iscsi_irmc share the same driver info
"agent_irmc": _iscsi_irmc_driver_info,
"pxe_wol": _pxe_wol_driver_info}
def _get_driver_info(node):
pm_type = node["pm_type"]
if "ipmi" in pm_type:
return _ipmi_driver_info(node)
else:
if pm_type in driver_info_map:
return driver_info_map[pm_type](node)
else:
raise ValueError("Unknown pm_type: %s" % node["pm_type"])
driver_info = _get_driver_info(node)
driver_info = _associate_deploy_kr_info(driver_info, node)
return driver_info
def register_ironic_node(service_host, node, client=None, blocking=True):
mapping = {'cpus': 'cpu',
'memory_mb': 'memory',
'local_gb': 'disk',
'cpu_arch': 'arch'}
properties = {k: six.text_type(node.get(v))
for k, v in mapping.items()
if node.get(v) is not None}
driver_info = _extract_driver_info(node)
if 'capabilities' in node:
properties.update({"capabilities":
six.text_type(node.get('capabilities'))})
create_map = {"driver": node["pm_type"],
"properties": properties,
"driver_info": driver_info}
if 'name' in node:
create_map.update({"name": six.text_type(node.get('name'))})
for count in range(60):
LOG.debug('Registering %s node with ironic, try #%d.' %
(node.get("pm_addr", ''), count))
try:
ironic_node = client.node.create(**create_map)
break
except (ironicexp.ConnectionRefused, ironicexp.ServiceUnavailable):
if blocking:
LOG.debug('Service not available, sleeping for 10 seconds.')
time.sleep(10)
else:
LOG.debug('Service not available.')
else:
if blocking:
LOG.debug('Service unavailable after 10 minutes, giving up.')
else:
LOG.debug('Service unavailable after 60 tries, giving up.')
raise ironicexp.ServiceUnavailable()
for mac in node["mac"]:
client.port.create(address=mac, node_uuid=ironic_node.uuid)
# Ironic should do this directly, see bug 1315225.
try:
client.node.set_power_state(ironic_node.uuid, 'off')
except ironicexp.Conflict:
# Conflict means the Ironic conductor got there first, so we can
# ignore the exception.
pass
return ironic_node
def _populate_node_mapping(client):
LOG.debug('Populating list of registered nodes.')
node_map = {'mac': {}, 'pm_addr': {}}
nodes = [n.to_dict() for n in client.node.list()]
for node in nodes:
node_details = client.node.get(node['uuid'])
if node_details.driver in ('pxe_ssh', 'fake_pxe'):
for port in client.node.list_ports(node['uuid']):
node_map['mac'][port.address] = node['uuid']
elif 'ipmi' in node_details.driver:
pm_addr = node_details.driver_info['ipmi_address']
node_map['pm_addr'][pm_addr] = node['uuid']
elif node_details.driver == 'pxe_ilo':
pm_addr = node_details.driver_info['ilo_address']
node_map['pm_addr'][pm_addr] = node['uuid']
elif node_details.driver == 'pxe_drac':
pm_addr = node_details.driver_info['drac_host']
node_map['pm_addr'][pm_addr] = node['uuid']
elif node_details.driver == 'pxe_iboot':
iboot_addr = node_details.driver_info['iboot_address']
if "iboot_port" in node_details.driver_info:
iboot_addr += (':%s' %
node_details.driver_info['iboot_port'])
if "iboot_relay_id" in node_details.driver_info:
iboot_addr += ('#%s' %
node_details.driver_info['iboot_relay_id'])
node_map['pm_addr'][iboot_addr] = node['uuid']
elif node_details.driver == 'pxe_irmc':
pm_addr = node_details.driver_info['irmc_address']
node_map['pm_addr'][pm_addr] = node['uuid']
return node_map
def _get_node_id(node, node_map):
if node['pm_type'] in ('pxe_ssh', 'fake_pxe'):
for mac in node['mac']:
if mac.lower() in node_map['mac']:
return node_map['mac'][mac.lower()]
elif node['pm_type'] == 'pxe_iboot':
iboot_addr = node["pm_addr"]
if "pm_port" in node:
iboot_addr += ':%s' % node["pm_port"]
if "pm_relay_id" in node:
iboot_addr += '#%s' % node["pm_relay_id"]
if iboot_addr in node_map['pm_addr']:
return node_map['pm_addr'][iboot_addr]
else:
if node['pm_addr'] in node_map['pm_addr']:
return node_map['pm_addr'][node['pm_addr']]
def _update_or_register_ironic_node(service_host, node, node_map, client=None,
blocking=True):
node_uuid = _get_node_id(node, node_map)
massage_map = {'cpu': '/properties/cpus',
'memory': '/properties/memory_mb',
'disk': '/properties/local_gb',
'arch': '/properties/cpu_arch'}
if "ipmi" in node['pm_type']:
massage_map.update({'pm_addr': '/driver_info/ipmi_address',
'pm_user': '/driver_info/ipmi_username',
'pm_password': '/driver_info/ipmi_password'})
elif node['pm_type'] == 'pxe_ssh':
massage_map.update({'pm_addr': '/driver_info/ssh_address',
'pm_user': '/driver_info/ssh_username',
'pm_password': '/driver_info/ssh_key_contents'})
elif node['pm_type'] == 'pxe_ilo':
massage_map.update({'pm_addr': '/driver_info/ilo_address',
'pm_user': '/driver_info/ilo_username',
'pm_password': '/driver_info/ilo_password'})
elif node['pm_type'] == 'pxe_drac':
massage_map.update({'pm_addr': '/driver_info/drac_host',
'pm_user': '/driver_info/drac_username',
'pm_password': '/driver_info/drac_password'})
elif node['pm_type'] == 'pxe_irmc':
massage_map.update({'pm_addr': '/driver_info/irmc_address',
'pm_user': '/driver_info/irmc_username',
'pm_password': '/driver_info/irmc_password'})
if "name" in node:
massage_map.update({'name': '/name'})
if "capabilities" in node:
massage_map.update({'capabilities': '/properties/capabilities'})
if node_uuid:
ironic_node = client.node.get(node_uuid)
else:
ironic_node = None
if ironic_node is None:
ironic_node = register_ironic_node(service_host, node, client,
blocking=blocking)
else:
LOG.debug('Node %s already registered, updating details.' % (
ironic_node.uuid))
node_patch = []
for key, value in massage_map.items():
node_patch.append({'path': value,
'value': six.text_type(node[key]),
'op': 'replace'})
for count in range(2):
try:
client.node.update(ironic_node.uuid, node_patch)
break
except ironicexp.Conflict:
LOG.debug('Node %s locked for updating.' %
ironic_node.uuid)
time.sleep(5)
else:
raise ironicexp.Conflict()
return ironic_node.uuid
def _clean_up_extra_nodes(seen, client, remove=False):
all_nodes = set([n.uuid for n in client.node.list()])
remove_func = client.node.delete
extra_nodes = all_nodes - seen
for node in extra_nodes:
if remove:
LOG.debug('Removing extra registered node %s.' % node)
remove_func(node)
else:
LOG.debug('Extra registered node %s found.' % node)
def _register_list_of_nodes(register_func, node_map, client, nodes_list,
blocking, service_host, kernel_id, ramdisk_id):
seen = set()
for node in nodes_list:
if kernel_id:
if 'kernel_id' not in node:
node['kernel_id'] = kernel_id
if ramdisk_id:
if 'ramdisk_id' not in node:
node['ramdisk_id'] = ramdisk_id
try:
new_node = register_func(service_host, node, node_map,
client=client, blocking=blocking)
seen.add(new_node)
except ironicexp.Conflict:
LOG.debug("Could not update node, moving to next host")
seen.add(node)
return seen
def register_all_nodes(service_host, nodes_list, client=None, remove=False,
blocking=True, keystone_client=None, glance_client=None,
kernel_name=None, ramdisk_name=None):
LOG.warning('Using register_all_nodes from os-cloud-config is deprecated, '
'please use the same function from tripleo_common.utils.nodes')
LOG.debug('Registering all nodes.')
if client is None:
LOG.warning('Creating ironic client inline is deprecated, please '
'pass the client as parameter.')
client = clients.get_ironic_client()
register_func = _update_or_register_ironic_node
node_map = _populate_node_mapping(client)
glance_ids = {'kernel': None, 'ramdisk': None}
if kernel_name and ramdisk_name:
if glance_client is None:
LOG.warning('Creating glance client inline is deprecated, please '
'pass the client as a parameter.')
client = clients.get_glance_client()
glance_ids = glance.create_or_find_kernel_and_ramdisk(
glance_client, kernel_name, ramdisk_name)
seen = _register_list_of_nodes(register_func, node_map, client,
nodes_list, blocking, service_host,
glance_ids['kernel'], glance_ids['ramdisk'])
_clean_up_extra_nodes(seen, client, remove=remove)