|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import collections |
| 4 | +import json |
| 5 | + |
| 6 | +import openstack |
| 7 | + |
| 8 | + |
| 9 | +def get_capacity_per_flavor(placement_client, flavors): |
| 10 | + capacity_per_flavor = {} |
| 11 | + |
| 12 | + for flavor in flavors: |
| 13 | + resources, traits = get_placement_request(flavor) |
| 14 | + max_per_host = get_max_per_host(placement_client, resources, traits) |
| 15 | + capacity_per_flavor[flavor.name] = max_per_host |
| 16 | + |
| 17 | + return capacity_per_flavor |
| 18 | + |
| 19 | + |
| 20 | +def get_placement_request(flavor): |
| 21 | + resources = {"MEMORY_MB": flavor.ram, "DISK_GB": (flavor.disk + flavor.ephemeral)} |
| 22 | + required_traits = [] |
| 23 | + for key, value in flavor.extra_specs.items(): |
| 24 | + if "trait:" == key[:6]: |
| 25 | + if value == "required": |
| 26 | + required_traits.append(key[6:]) |
| 27 | + if "resources:" == key[:10]: |
| 28 | + count = int(value) |
| 29 | + resources[key[10:]] = count |
| 30 | + if "hw:cpu_policy" == key and value == "dedicated": |
| 31 | + resources["PCPU"] = flavor.vcpus |
| 32 | + if "PCPU" not in resources.keys() and "VCPU" not in resources.keys(): |
| 33 | + resources["VCPU"] = flavor.vcpus |
| 34 | + return resources, required_traits |
| 35 | + |
| 36 | + |
| 37 | +def get_max_per_host(placement_client, resources, required_traits): |
| 38 | + resource_str = ",".join( |
| 39 | + [key + ":" + str(value) for key, value in resources.items() if value] |
| 40 | + ) |
| 41 | + required_str = ",".join(required_traits) |
| 42 | + # TODO(johngarbut): remove disabled! |
| 43 | + forbidden_str = "COMPUTE_STATUS_DISABLED" |
| 44 | + |
| 45 | + response = placement_client.get( |
| 46 | + "/allocation_candidates", |
| 47 | + params={"resources": resource_str, "required": required_str}, |
| 48 | + headers={"OpenStack-API-Version": "placement 1.29"}, |
| 49 | + ) |
| 50 | + raw_data = response.json() |
| 51 | + count_per_rp = {} |
| 52 | + for rp_uuid, summary in raw_data.get("provider_summaries", {}).items(): |
| 53 | + # per resource, get max possible number of instances |
| 54 | + max_counts = [] |
| 55 | + for resource, amounts in summary["resources"].items(): |
| 56 | + requested = resources.get(resource, 0) |
| 57 | + if requested: |
| 58 | + free = amounts["capacity"] - amounts["used"] |
| 59 | + amount = int(free / requested) |
| 60 | + max_counts.append(amount) |
| 61 | + # available count is the min of the max counts |
| 62 | + if max_counts: |
| 63 | + count_per_rp[rp_uuid] = min(max_counts) |
| 64 | + |
| 65 | + return count_per_rp |
| 66 | + |
| 67 | + |
| 68 | +def print_exporter_data(app): |
| 69 | + flavors = list(app.compute_client.flavors()) |
| 70 | + capacity_per_flavor = get_capacity_per_flavor(app.placement_client, flavors) |
| 71 | + |
| 72 | + # total capacity per flavor |
| 73 | + flavor_names = sorted([f.name for f in flavors]) |
| 74 | + for flavor_name in flavor_names: |
| 75 | + counts = capacity_per_flavor.get(flavor_name, {}).values() |
| 76 | + total = 0 if not counts else sum(counts) |
| 77 | + print(f'openstack_total_capacity_per_flavor{{flavor="{flavor_name}"}} {total}') |
| 78 | + |
| 79 | + # capacity per host |
| 80 | + raw_rps = list(app.placement_client.resource_providers()) |
| 81 | + resource_providers = {rp.name: rp.id for rp in raw_rps} |
| 82 | + hostnames = sorted(resource_providers.keys()) |
| 83 | + for hostname in hostnames: |
| 84 | + rp_id = resource_providers[hostname] |
| 85 | + for flavor_name in flavor_names: |
| 86 | + all_counts = capacity_per_flavor.get(flavor_name, {}) |
| 87 | + our_count = all_counts.get(rp_id, 0) |
| 88 | + if our_count == 0: |
| 89 | + continue |
| 90 | + print( |
| 91 | + f'openstack_capacity_by_hostname{{hypervisor="{hostname}",flavor="{flavor_name}"}} {our_count}' |
| 92 | + ) |
0 commit comments