openstack-virtual-baremetal/openstack_virtual_baremetal/build_nodes_json.py
Ben Nemec 53320ee72b Re-enable uefi support with glanceclient
Use glanceclient to examine image metadata instead of the nova
image proxy, which seems to have been removed in recent versions of
novaclient.
2017-04-26 12:55:22 -05:00

260 lines
10 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright 2015 Red Hat Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import json
import os
import sys
import yaml
import glanceclient
from neutronclient.v2_0 import client as neutronclient
import novaclient as nc
from novaclient import client as novaclient
import auth
def _parse_args():
parser = argparse.ArgumentParser(
prog='build-nodes-json.py',
description='Tool for collecting virtual IPMI details',
)
parser.add_argument('--env',
dest='env',
default=None,
help='YAML file containing OVB environment details')
parser.add_argument('--bmc_prefix',
dest='bmc_prefix',
default='bmc',
help='BMC name prefix')
parser.add_argument('--baremetal_prefix',
dest='baremetal_prefix',
default='baremetal',
help='Baremetal name prefix')
parser.add_argument('--private_net',
dest='private_net',
default='private',
help='DEPRECATED: This parameter is ignored.')
parser.add_argument('--provision_net',
dest='provision_net',
default='provision',
help='Provisioning network name')
parser.add_argument('--nodes_json',
dest='nodes_json',
default='nodes.json',
help='Destination to store the nodes json file to')
parser.add_argument('--add_undercloud',
dest='add_undercloud',
action='store_true',
help='Adds the undercloud details to the json file.')
args = parser.parse_args()
return args
def _get_names(args):
undercloud_name = None
if args.env is None:
bmc_base = args.bmc_prefix
baremetal_base = args.baremetal_prefix
provision_net = args.provision_net
if args.add_undercloud:
undercloud_name = 'undercloud'
else:
with open(args.env) as f:
e = yaml.safe_load(f)
bmc_base = e['parameters']['bmc_prefix']
baremetal_base = e['parameters']['baremetal_prefix']
provision_net = e['parameters']['provision_net']
role = e['parameter_defaults'].get('role')
if role and baremetal_base.endswith('-' + role):
baremetal_base = baremetal_base[:-len(role) - 1]
if args.add_undercloud:
undercloud_name = e['parameters']['undercloud_name']
return bmc_base, baremetal_base, provision_net, undercloud_name
def _get_clients():
cloud = os.environ.get('OS_CLOUD')
if cloud:
import os_client_config
nova = os_client_config.make_client('compute', cloud=cloud)
neutron = os_client_config.make_client('network', cloud=cloud)
glance = os_client_config.make_client('image', cloud=cloud)
else:
auth_data = auth._create_auth_parameters()
username = auth_data['os_user']
password = auth_data['os_password']
tenant = auth_data['os_tenant']
auth_url = auth_data['os_auth_url']
project = auth_data['os_project']
user_domain = auth_data['os_user_domain']
project_domain = auth_data['os_project_domain']
if '/v3' not in auth_url:
# novaclient 7+ is backwards-incompatible :-(
if int(nc.__version__[0]) <= 6:
nova = novaclient.Client(2, username, password, tenant, auth_url)
else:
nova = novaclient.Client(2, username, password,
auth_url=auth_url,
project_name=tenant)
neutron = neutronclient.Client(
username=username,
password=password,
tenant_name=tenant,
auth_url=auth_url
)
else:
nova = novaclient.Client(2, username, password,
auth_url=auth_url,
project_name=project,
user_domain_name=user_domain,
project_domain_name=project_domain)
sess = auth._get_keystone_session(auth_data)
neutron = neutronclient.Client(session=sess)
token, glance_endpoint = auth._get_token_and_endpoint('glance')
glance = glanceclient.Client('2', token=token,
endpoint=glance_endpoint)
return nova, neutron, glance
def _get_ports(neutron, bmc_base, baremetal_base):
all_ports = sorted(neutron.list_ports()['ports'], key=lambda x: x['name'])
bmc_ports = list([p for p in all_ports
if p['name'].startswith(bmc_base)])
bm_ports = list([p for p in all_ports
if p['name'].startswith(baremetal_base)])
if len(bmc_ports) != len(bm_ports):
raise RuntimeError('Found different numbers of baremetal and '
'bmc ports. bmc: %s baremetal: %s' % (bmc_ports,
bm_ports))
return bmc_ports, bm_ports
def _build_nodes(nova, glance, bmc_ports, bm_ports, provision_net,
baremetal_base, undercloud_name):
node_template = {
'pm_type': 'pxe_ipmitool',
'mac': '',
'cpu': '',
'memory': '',
'disk': '',
'arch': 'x86_64',
'pm_user': 'admin',
'pm_password': 'password',
'pm_addr': '',
'capabilities': 'boot_option:local',
'name': '',
}
nodes = []
bmc_bm_pairs = []
cache = {}
for bmc_port, baremetal_port in zip(bmc_ports, bm_ports):
baremetal = nova.servers.get(baremetal_port['device_id'])
node = dict(node_template)
node['pm_addr'] = bmc_port['fixed_ips'][0]['ip_address']
bmc_bm_pairs.append((node['pm_addr'], baremetal.name))
node['mac'] = [baremetal.addresses[provision_net][0]['OS-EXT-IPS-MAC:mac_addr']]
if not cache.get(baremetal.flavor['id']):
cache[baremetal.flavor['id']] = nova.flavors.get(baremetal.flavor['id'])
flavor = cache.get(baremetal.flavor['id'])
node['cpu'] = flavor.vcpus
node['memory'] = flavor.ram
node['disk'] = flavor.disk
# NOTE(bnemec): Older versions of Ironic won't allow _ characters in
# node names, so translate to the allowed character -
node['name'] = baremetal.name.replace('_', '-')
# If a node has uefi firmware ironic needs to be aware of this, in nova
# this is set using a image property called "hw_firmware_type"
if not cache.get(baremetal.image['id']):
cache[baremetal.image['id']] = glance.images.get(baremetal.image['id'])
image = cache.get(baremetal.image['id'])
if image.get('hw_firmware_type') == 'uefi':
node['capabilities'] += ",boot_mode:uefi"
bm_name_end = baremetal.name[len(baremetal_base):]
if '-' in bm_name_end:
profile = bm_name_end[1:].split('_')[0]
node['capabilities'] += ',profile:%s' % profile
nodes.append(node)
extra_nodes = []
if undercloud_name:
undercloud_node_template = {
'name': undercloud_name,
'id': '',
'ips': [],
}
try:
undercloud_instance = nova.servers.list(
search_opts={'name': undercloud_name})[0]
except IndexError:
raise RuntimeError(
'undercloud %s specified in the environment file is not '
'available in nova' % undercloud_name)
undercloud_node_template['id'] = undercloud_instance.id
undercloud_node_template['ips'] = nova.servers.ips(undercloud_instance)
extra_nodes.append(undercloud_node_template)
return nodes, bmc_bm_pairs, extra_nodes
def _write_nodes(nodes, extra_nodes, args):
with open(args.nodes_json, 'w') as node_file:
resulting_json = {'nodes': nodes}
if extra_nodes:
resulting_json['extra_nodes'] = extra_nodes
contents = json.dumps(resulting_json, indent=2)
node_file.write(contents)
print(contents)
print('Wrote node definitions to %s' % args.nodes_json)
# TODO(bnemec): parameterize this based on args.nodes_json
def _write_pairs(bmc_bm_pairs):
filename = 'bmc_bm_pairs'
with open(filename, 'w') as pairs_file:
pairs_file.write('# A list of BMC addresses and the name of the '
'instance that BMC manages.\n')
for i in bmc_bm_pairs:
pair = '%s %s' % i
pairs_file.write(pair + '\n')
print(pair)
print('Wrote BMC to instance mapping file to %s' % filename)
def main():
args = _parse_args()
bmc_base, baremetal_base, provision_net, undercloud_name = _get_names(args)
nova, neutron, glance = _get_clients()
bmc_ports, bm_ports = _get_ports(neutron, bmc_base, baremetal_base)
nodes, bmc_bm_pairs, extra_nodes = _build_nodes(nova, glance, bmc_ports,
bm_ports,
provision_net,
baremetal_base,
undercloud_name)
_write_nodes(nodes, extra_nodes, args)
_write_pairs(bmc_bm_pairs)
if __name__ == '__main__':
main()