vino/vino-builder/assets/playbooks/roles/libvirt/library/core_allocation.py

159 lines
5.4 KiB
Python

#!/usr/bin/python
# 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.
# generate_baremetal_macs method ripped from
# openstack/tripleo-incubator/scripts/configure-vm
import math
import random
import sys
import fnmatch
import os
from itertools import chain
import json
DOCUMENTATION = '''
---
module: core_allocation
version_added: "1.0"
short_description: Allocate numa aligned cores for libvirt domains and track allocations
description:
- Generate numa aligned cores for libvirt domains and track allocations
'''
PATH_SYS_DEVICES_NODE = "/sys/devices/system/node"
def _parse_range(rng):
parts = rng.split('-')
if 1 > len(parts) > 2:
raise ValueError("Bad range: '%s'" % (rng,))
parts = [int(i) for i in parts]
start = parts[0]
end = start if len(parts) == 1 else parts[1]
if start > end:
end, start = start, end
return range(start, end + 1)
def _parse_range_list(rngs):
return sorted(set(chain(*[_parse_range(rng) for rng in rngs.split(',')])))
def get_numa_cores():
"""Return cores as a dict of numas each with their expanded core lists"""
numa_core_dict = {}
for root, dir, files in os.walk(PATH_SYS_DEVICES_NODE):
for numa in fnmatch.filter(dir, "node*"):
numa_path = os.path.join(PATH_SYS_DEVICES_NODE, numa)
cpulist = os.path.join(numa_path, "cpulist")
with open(cpulist, 'r') as f:
parsed_range_list = _parse_range_list(f.read())
numa_core_dict[numa] = parsed_range_list
return numa_core_dict
def allocate_cores(nodes, flavors, exclude_cpu):
"""Return"""
core_state = {}
try:
f = open('/etc/libvirt/vino-cores.json', 'r')
core_state = json.loads(f.read())
except:
pass
# instantiate initial inventory - we don't support the inventory
# changing (e.g. adding cores)
if 'inventory' not in core_state:
core_state['inventory'] = get_numa_cores()
# explode exclude cpu list - we don't support adjusting this after-the-fact
# right now
if 'exclude' not in core_state:
exclude_core_list = _parse_range_list(exclude_cpu)
core_state['exclude'] = exclude_core_list
# reduce inventory by exclude
if 'available' not in core_state:
core_state['available'] = {}
for numa in core_state['inventory'].keys():
numa_available = [x for x in core_state['inventory'][numa] if x not in core_state['exclude']]
core_state['available'][numa] = numa_available
if 'assignments' not in core_state:
core_state['assignments'] = {}
# walk the nodes, consuming inventory or discovering previous allocations
# address the case where previous != desired - delete previous, re-run
for node in nodes:
flavor = node["role"]
vcpus = flavors[flavor]['vcpus']
# generate a unique name such as master-0, master-1
node_name = node["name"]
# extract the core count
core_count = int(vcpus)
# discover any previous allocation
if 'assignments' in core_state:
if node_name in core_state['assignments']:
if len(core_state['assignments'][node_name]) == core_count:
continue
else:
# TODO: support releasing the cores and adding them back
# to available
raise Exception("Existing assignment exists for node %s but does not match current core count needed" % node_name)
# allocate the cores
allocated=False
for numa in core_state['available']:
if core_count <= len(core_state['available'][numa]):
allocated=True
cores_to_use = core_state['available'][numa][:core_count]
core_state['assignments'][node_name] = cores_to_use
core_state['available'][numa] = core_state['available'][numa][core_count:]
break
else:
continue
if not allocated:
raise Exception("Unable to find sufficient cores (%s) for node %s (available was %r)" % (core_count, node_name, core_state['available']))
# return a dict of nodes: cores
# or error if insufficient
with open('/etc/libvirt/vino-cores.json', 'w') as f:
f.write(json.dumps(core_state))
return core_state['assignments']
def main():
module = AnsibleModule(
argument_spec=dict(
nodes=dict(required=True, type='list'),
flavors=dict(required=True, type='dict'),
exclude_cpu=dict(required=True, type='str')
)
)
result = allocate_cores(module.params["nodes"],
module.params["flavors"],
module.params["exclude_cpu"])
module.exit_json(**result)
# see http://docs.ansible.com/developing_modules.html#common-module-boilerplate
from ansible.module_utils.basic import AnsibleModule # noqa
if __name__ == '__main__':
main()