Skip to content

Commit a299948

Browse files
authored
Merge pull request #5 from stackhpc/placement
Add Prometheus exporter for usage
2 parents 03c3246 + c80f6fb commit a299948

File tree

11 files changed

+132
-39
lines changed

11 files changed

+132
-39
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ nosetests.xml
4545
coverage.xml
4646
*.cover
4747
.hypothesis/
48+
.stestr
4849

4950
# Translations
5051
*.mo

.stestr.conf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[DEFAULT]
2+
test_path=./os_capacity/tests
3+
top_dir=./

README.rst

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -135,23 +135,8 @@ See the online help for more details:
135135
complete print bash completion command
136136
flavor list List all the flavors.
137137
help print detailed help for another command
138+
prometheus To be run as node exporter textfile collector
138139
resources all List all resource providers, with their resources and servers.
139140
resources group Lists counts of resource providers with similar inventories.
140141
usages all List all current resource usages.
141142
usages group Group usage by specified key (by user or project).
142-
143-
Submitting Metrics to Monasca
144-
-----------------------------
145-
146-
There is now an experimental mode where metrics can be written into Monasca
147-
for the calls "resources group" and "usages group user". To enable this
148-
feature you must set::
149-
150-
export OS_CAPACITY_SEND_METRICS=1
151-
152-
To later disable the feature you must unset the environment variable::
153-
154-
unset OS_CAPACITY_SEND_METRICS
155-
156-
For an example of using this with cron, please see the example script in
157-
``cron/example.sh``.

os_capacity/commands/commands.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919

2020
from os_capacity.data import metrics
2121
from os_capacity import utils
22+
from os_capacity.data import candidates
23+
24+
25+
class PrometheusAll(Lister):
26+
"""To be run as node exporter textfile collector."""
27+
def take_action(self, parsed_args):
28+
candidates.print_exporter_data(self.app)
29+
# TODO(johngarbutt) a total hack!
30+
return (('fake'), [])
2231

2332

2433
class FlavorList(Lister):

os_capacity/data/candidates.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
)

os_capacity/shell.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from cliff.commandmanager import CommandManager
1919
import os_client_config
2020

21+
import openstack
2122

2223
def get_cloud_config():
2324
# TODO(johngarbutt) consider passing in argument parser
@@ -41,11 +42,11 @@ def __init__(self):
4142
def initialize_app(self, argv):
4243
self.LOG.debug('initialize_app')
4344

44-
config = os_client_config.get_config()
45-
self.compute_client = config.get_session_client("compute")
46-
self.placement_client = config.get_session_client("placement")
47-
self.monitoring_client = config.get_session_client("monitoring")
48-
self.identity_client = config.get_session_client("identity")
45+
conn = openstack.openstack.connect()
46+
self.connection = conn
47+
self.compute_client = conn.compute
48+
self.placement_client = conn.placement
49+
self.identity_client = conn.identity
4950

5051
self.LOG.debug('setup Keystone API REST clients')
5152

os_capacity/tests/unit/test_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# under the License.
1414

1515
import datetime
16-
import mock
1716
import unittest
17+
from unittest import mock
1818

1919
from os_capacity.data import flavors
2020
from os_capacity.data import resource_provider

os_capacity/tests/unit/test_utils.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# under the License.
1414

1515
import datetime
16-
import mock
1716
import unittest
17+
from unittest import mock
1818

1919
from os_capacity.data import flavors
2020
from os_capacity.data import resource_provider
@@ -26,15 +26,18 @@ class TestUtils(unittest.TestCase):
2626

2727
@mock.patch.object(flavors, "get_all")
2828
def test_get_flavors(self, mock_flavors):
29-
mock_flavors.return_value = [flavors.Flavor(
30-
id="id", name="name", vcpus=1, ram_mb=2, disk_gb=3)]
29+
mock_list = [flavors.Flavor(
30+
id="id", name="name", vcpus=1, ram_mb=2, disk_gb=3,
31+
extra_specs={})]
32+
mock_flavors.return_value = mock_list
3133
app = mock.Mock()
3234

3335
result = utils.get_flavors(app)
3436

35-
mock_flavors.assert_called_once_with(app.compute_client)
36-
expected_flavors = [('id', 'name', 1, 2, 3)]
37-
self.assertEqual(expected_flavors, result)
37+
mock_flavors.assert_called_once_with(
38+
app.compute_client, include_extra_specs=True)
39+
expected_flavors = [('id', 'name', 1, 2, 3, {})]
40+
self.assertEqual(mock_list, result)
3841

3942
@mock.patch.object(resource_provider, 'get_allocations')
4043
@mock.patch.object(resource_provider, 'get_inventories')

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
'.commands:ListResourcesGroups',
5858
'usages_all = os_capacity.commands.commands:ListUsagesAll',
5959
'usages_group = os_capacity.commands.commands:ListUsagesGroup',
60+
'prometheus = os_capacity.commands.commands:PrometheusAll',
6061
],
6162
},
6263
)

test-requirements.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# The order of packages is significant, because pip processes them in the order
22
# of appearance. Changing the order has an impact on the overall integration
33
# process, which may cause wedges in the gate later.
4+
hacking>=3.0,<3.1 # Apache-2.0
45

5-
hacking>=0.12.0,<0.13 # Apache-2.0
6-
7-
coverage>=4.0 # Apache-2.0
8-
doc8 # Apache-2.0
9-
sphinx>=1.5.1 # BSD
6+
black
7+
coverage>=4.0,!=4.4 # Apache-2.0
8+
python-subunit>=0.0.18 # Apache-2.0/BSD
109
oslotest>=1.10.0 # Apache-2.0
10+
stestr>=1.0.0 # Apache-2.0
11+
testtools>=1.4.0 # MIT

0 commit comments

Comments
 (0)