159 lines
5.4 KiB
Python
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() |