diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index fbfcc21..34fcf65 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -3,24 +3,20 @@ name: Docker image
on: push
jobs:
build_push_api:
- name: Build and push execution environment
+ name: Build and push image
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write # needed for signing the images with GitHub OIDC Token
+ packages: write # required for pushing container images
+ security-events: write # required for pushing SARIF files
+
steps:
- name: Check out the repository
- uses: actions/checkout@v2
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
+ uses: actions/checkout@v4
- - name: Set up Docker layer caching
- uses: actions/cache@v2
- with:
- path: /tmp/.buildx-cache
- key: ${{ runner.os }}-buildx-${{ github.sha }}
- restore-keys: |
- ${{ runner.os }}-buildx-
- name: Login to GitHub Container Registry
- uses: docker/login-action@v1
+ uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -28,7 +24,7 @@ jobs:
- name: Calculate metadata for image
id: image-meta
- uses: docker/metadata-action@v3
+ uses: docker/metadata-action@v5
with:
images: ghcr.io/stackhpc/os-capacity
# Produce the branch name or tag and the SHA as tags
@@ -36,21 +32,14 @@ jobs:
type=ref,event=branch
type=ref,event=tag
type=sha,prefix=
+
- name: Build and push image
- uses: docker/build-push-action@v2
+ uses: azimuth-cloud/github-actions/docker-multiarch-build-push@master
with:
+ cache-key: os-capacity
context: .
+ platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.image-meta.outputs.tags }}
labels: ${{ steps.image-meta.outputs.labels }}
- cache-from: type=local,src=/tmp/.buildx-cache
- cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
-
- # Temp fix
- # https://github.com/docker/build-push-action/issues/252
- # https://github.com/moby/buildkit/issues/1896
- # https://github.com/docker/buildx/pull/535
- - name: Move cache
- run: |
- rm -rf /tmp/.buildx-cache
- mv /tmp/.buildx-cache-new /tmp/.buildx-cache
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml
new file mode 100644
index 0000000..82bd824
--- /dev/null
+++ b/.github/workflows/tox.yaml
@@ -0,0 +1,27 @@
+name: Tox unit tests
+
+on: push
+
+jobs:
+ build:
+ name: Tox unit tests and linting
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ['3.10']
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install tox
+
+ - name: Test with tox
+ run: tox
diff --git a/README.rst b/README.rst
index 357461f..48a56d6 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,11 @@
os-capacity
===========
-This is a prototype tool to extract capacity information.
+This is a prototype prometheus exporter
+to extract capacity information from OpenStack Placement.
+
+It includes support for both baremetal flavors
+and flavors with PCPU resources implied.
Install
-------
diff --git a/cron/example.sh b/cron/example.sh
deleted file mode 100755
index 98acd25..0000000
--- a/cron/example.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Example script to use with cron to regular send metrics to Monasca
-#
-
-# Example crontab entry to run this script every 5 mins:
-#
-# */5 * * * * /home/stack/os-capacity/cron/example.sh > /tmp/metrics_last.log
-
-set -e
-
-source /home/stack/os-capacity/.openrc
-source /home/stack/os-capacity/.venv-test/bin/activate
-
-export OS_CAPACITY_SEND_METRICS=1
-
-os-capacity usages group
-os-capacity resources group
diff --git a/cron/grafana_dashboard.json b/cron/grafana_dashboard.json
deleted file mode 100644
index 75fd35a..0000000
--- a/cron/grafana_dashboard.json
+++ /dev/null
@@ -1,524 +0,0 @@
-{
- "__inputs": [
- {
- "name": "DS_MONASCA-METRICS",
- "label": "Monasca-metrics",
- "description": "",
- "type": "datasource",
- "pluginId": "monasca-datasource",
- "pluginName": "Monasca"
- }
- ],
- "__requires": [
- {
- "type": "panel",
- "id": "table",
- "name": "Table",
- "version": ""
- },
- {
- "type": "panel",
- "id": "graph",
- "name": "Graph",
- "version": ""
- },
- {
- "type": "grafana",
- "id": "grafana",
- "name": "Grafana",
- "version": "4.1.0-pre1"
- },
- {
- "type": "datasource",
- "id": "monasca-datasource",
- "name": "Monasca",
- "version": "1.0.0"
- }
- ],
- "id": null,
- "title": "Resource Usage Dashboard",
- "tags": [],
- "style": "dark",
- "timezone": "browser",
- "editable": true,
- "sharedCrosshair": true,
- "hideControls": true,
- "time": {
- "from": "now-6h",
- "to": "now"
- },
- "timepicker": {
- "refresh_intervals": [
- "5s",
- "10s",
- "30s",
- "1m",
- "5m",
- "15m",
- "30m",
- "1h",
- "2h",
- "1d"
- ],
- "time_options": [
- "5m",
- "15m",
- "1h",
- "6h",
- "12h",
- "24h",
- "2d",
- "7d",
- "30d"
- ]
- },
- "templating": {
- "list": []
- },
- "annotations": {
- "list": []
- },
- "refresh": false,
- "schemaVersion": 13,
- "version": 12,
- "links": [],
- "gnetId": null,
- "rows": [
- {
- "title": "Dashboard Row",
- "panels": [
- {
- "columns": [
- {
- "text": "Current",
- "value": "current"
- }
- ],
- "datasource": "${DS_MONASCA-METRICS}",
- "editable": true,
- "error": false,
- "fontSize": "100%",
- "id": 2,
- "links": [],
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": null,
- "desc": false
- },
- "span": 4,
- "styles": [
- {
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "colorMode": "cell",
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 176, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "decimals": 0,
- "pattern": "/.*/",
- "thresholds": [
- "0.1",
- "1.1"
- ],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "aggregator": "max",
- "alias": "@flavor",
- "dimensions": [
- {
- "key": "flavor",
- "value": "$all"
- }
- ],
- "error": "",
- "group": true,
- "metric": "os_capacity.resources.free",
- "period": "300",
- "refId": "A"
- }
- ],
- "title": "Free Resources",
- "transform": "timeseries_aggregations",
- "type": "table"
- },
- {
- "columns": [
- {
- "text": "Current",
- "value": "current"
- }
- ],
- "datasource": "${DS_MONASCA-METRICS}",
- "editable": true,
- "error": false,
- "fontSize": "100%",
- "id": 3,
- "links": [],
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": null,
- "desc": false
- },
- "span": 4,
- "styles": [
- {
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "decimals": 0,
- "pattern": "/.*/",
- "thresholds": [
- "0"
- ],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "aggregator": "max",
- "alias": "@flavor",
- "dimensions": [
- {
- "key": "flavor",
- "value": "$all"
- }
- ],
- "error": "",
- "group": true,
- "metric": "os_capacity.resources.used",
- "period": "300",
- "refId": "A"
- }
- ],
- "title": "Used Resources",
- "transform": "timeseries_aggregations",
- "type": "table"
- },
- {
- "columns": [
- {
- "text": "Current",
- "value": "current"
- }
- ],
- "datasource": "${DS_MONASCA-METRICS}",
- "editable": true,
- "error": false,
- "fontSize": "100%",
- "id": 4,
- "links": [],
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": null,
- "desc": false
- },
- "span": 4,
- "styles": [
- {
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "decimals": 0,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "aggregator": "max",
- "alias": "@flavor",
- "dimensions": [
- {
- "key": "flavor",
- "value": "$all"
- }
- ],
- "error": "",
- "group": true,
- "metric": "os_capacity.resources.total",
- "period": "300",
- "refId": "A"
- }
- ],
- "title": "Total Resources in System",
- "transform": "timeseries_aggregations",
- "type": "table"
- }
- ],
- "showTitle": false,
- "titleSize": "h6",
- "height": 250,
- "repeat": null,
- "repeatRowId": null,
- "repeatIteration": null,
- "collapse": false
- },
- {
- "title": "Dashboard Row",
- "panels": [
- {
- "aliasColors": {},
- "bars": false,
- "datasource": "${DS_MONASCA-METRICS}",
- "editable": true,
- "error": false,
- "fill": 1,
- "id": 1,
- "legend": {
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "show": true,
- "total": false,
- "values": false
- },
- "lines": true,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "connected",
- "percentage": false,
- "pointradius": 5,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "span": 4,
- "stack": true,
- "steppedLine": false,
- "targets": [
- {
- "aggregator": "none",
- "alias": "@flavor",
- "dimensions": [],
- "error": "",
- "group": true,
- "hide": false,
- "metric": "os_capacity.resources.used",
- "period": "300",
- "refId": "A"
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeShift": null,
- "title": "Servers Used per Flavor",
- "tooltip": {
- "msResolution": false,
- "shared": true,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "mode": "time",
- "name": null,
- "show": true,
- "values": []
- },
- "yaxes": [
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- }
- ]
- },
- {
- "aliasColors": {},
- "bars": false,
- "datasource": "${DS_MONASCA-METRICS}",
- "editable": true,
- "error": false,
- "fill": 1,
- "id": 5,
- "legend": {
- "avg": false,
- "current": false,
- "max": false,
- "min": false,
- "show": true,
- "total": false,
- "values": false
- },
- "lines": true,
- "linewidth": 1,
- "links": [],
- "nullPointMode": "connected",
- "percentage": false,
- "pointradius": 5,
- "points": false,
- "renderer": "flot",
- "seriesOverrides": [],
- "span": 4,
- "stack": true,
- "steppedLine": false,
- "targets": [
- {
- "aggregator": "none",
- "alias": "@username",
- "dimensions": [],
- "error": "",
- "group": true,
- "hide": false,
- "metric": "os_capacity.usage.user.count",
- "period": "300",
- "refId": "A"
- }
- ],
- "thresholds": [],
- "timeFrom": null,
- "timeShift": null,
- "title": "Servers Used per User",
- "tooltip": {
- "msResolution": false,
- "shared": true,
- "sort": 0,
- "value_type": "individual"
- },
- "type": "graph",
- "xaxis": {
- "mode": "time",
- "name": null,
- "show": true,
- "values": []
- },
- "yaxes": [
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- },
- {
- "format": "short",
- "label": null,
- "logBase": 1,
- "max": null,
- "min": null,
- "show": true
- }
- ]
- },
- {
- "columns": [
- {
- "text": "Current",
- "value": "current"
- }
- ],
- "datasource": "${DS_MONASCA-METRICS}",
- "editable": true,
- "error": false,
- "fontSize": "100%",
- "id": 7,
- "links": [],
- "pageSize": null,
- "scroll": true,
- "showHeader": true,
- "sort": {
- "col": 1,
- "desc": true
- },
- "span": 4,
- "styles": [
- {
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "pattern": "Time",
- "type": "date"
- },
- {
- "colorMode": null,
- "colors": [
- "rgba(245, 54, 54, 0.9)",
- "rgba(237, 129, 40, 0.89)",
- "rgba(50, 172, 45, 0.97)"
- ],
- "dateFormat": "YYYY-MM-DD HH:mm:ss",
- "decimals": 0,
- "pattern": "/.*/",
- "thresholds": [],
- "type": "number",
- "unit": "short"
- }
- ],
- "targets": [
- {
- "aggregator": "max",
- "alias": "@username",
- "dimensions": [
- {
- "key": "flavor",
- "value": "$all"
- }
- ],
- "error": "",
- "group": true,
- "metric": "os_capacity.usage.user.days.count",
- "period": "300",
- "refId": "A"
- }
- ],
- "title": "Number of Server Days per User",
- "transform": "timeseries_aggregations",
- "type": "table"
- }
- ],
- "showTitle": false,
- "titleSize": "h6",
- "height": "250px",
- "repeat": null,
- "repeatRowId": null,
- "repeatIteration": null,
- "collapse": false
- }
- ]
-}
diff --git a/doc/source/conf.py b/doc/source/conf.py
deleted file mode 100644
index e492ae3..0000000
--- a/doc/source/conf.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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.
-
-# -*- coding: utf-8 -*-
-# 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 os
-import sys
-
-sys.path.insert(0, os.path.abspath('../..'))
-# -- General configuration ----------------------------------------------------
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = [
- 'sphinx.ext.autodoc',
- #'sphinx.ext.intersphinx',
- # Uncomment this to enable the OpenStack documentation style, adding
- # oslosphinx to test-requirements.txt.
- #'oslosphinx',
-]
-
-# autodoc generation is a bit aggressive and a nuisance when doing heavy
-# text edit cycles.
-# execute "export SPHINX_DEBUG=1" in your terminal to disable
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'os-capacity'
-copyright = u'2017, StackHPC Ltd.'
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-add_module_names = True
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# -- Options for HTML output --------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. Major themes that come with
-# Sphinx are currently 'default' and 'sphinxdoc'.
-# html_theme_path = ["."]
-# html_theme = '_theme'
-# html_static_path = ['static']
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = '%sdoc' % project
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass
-# [howto/manual]).
-latex_documents = [
- ('index',
- '%s.tex' % project,
- u'%s Documentation' % project,
- u'OpenStack Foundation', 'manual'),
-]
-
-# Example configuration for intersphinx: refer to the Python standard library.
-#intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/doc/source/development.rst b/doc/source/development.rst
deleted file mode 100644
index 3982e78..0000000
--- a/doc/source/development.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-TODO development
-================
diff --git a/doc/source/index.rst b/doc/source/index.rst
deleted file mode 100644
index ee8a183..0000000
--- a/doc/source/index.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-Welcome to os-capacity's documentation!
-=======================================
-
-.. include:: ../../README.rst
-
-Documentation
--------------
-
-.. note::
-
- nothing to see here yet :(
-
-.. toctree::
- :maxdepth: 2
-
- installation
- usage
-
-Developer Documentation
------------------------
-
-.. toctree::
- :maxdepth: 2
-
- development
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
deleted file mode 100644
index 30a8521..0000000
--- a/doc/source/installation.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-TODO: Install
-=============
diff --git a/doc/source/usage.rst b/doc/source/usage.rst
deleted file mode 100644
index 2adda45..0000000
--- a/doc/source/usage.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-TODO: usage docs
-================
diff --git a/horizon/_footer.html.example b/horizon/_footer.html.example
deleted file mode 100644
index 735fdd2..0000000
--- a/horizon/_footer.html.example
+++ /dev/null
@@ -1,56 +0,0 @@
-
Capacity
-
-Note: this is stale data from Thu 7 Sep 13:04:05 BST 2017
-
-
-
-
- Flavors |
- Free |
- Used |
- Total |
-
-
-
-
-
-
-
diff --git a/horizon/config.json.example b/horizon/config.json.example
deleted file mode 100644
index 7879b82..0000000
--- a/horizon/config.json.example
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "command": "/usr/sbin/httpd -DFOREGROUND",
- "config_files": [
- {
- "source": "/var/lib/kolla/config_files/horizon.conf",
- "dest": "/etc/httpd/conf.d/horizon.conf",
- "owner": "horizon",
- "perm": "0600"
- },
- {
- "source": "/var/lib/kolla/config_files/_footer.html",
- "dest": "/usr/share/openstack-dashboard/openstack_dashboard/templates/_login_footer.html",
- "owner": "horizon",
- "perm": "0600"
- },
-...
-}
diff --git a/horizon/generate-footer.sh b/horizon/generate-footer.sh
deleted file mode 100755
index 030aede..0000000
--- a/horizon/generate-footer.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-set -ex
-
-CAPACITY=`os-capacity resources group -f json`
-CURRENT_DATE=`date`
-
-cat >_footer.html <Capacity
-
-Note: this is stale data from $CURRENT_DATE
-
-
-
-
- Flavors |
- Free |
- Used |
- Total |
-
-
-
-
-
-
-
-EOF
-
-cat _footer.html
diff --git a/os_capacity/commands/__init__.py b/os_capacity/commands/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/os_capacity/commands/commands.py b/os_capacity/commands/commands.py
deleted file mode 100644
index 2c56858..0000000
--- a/os_capacity/commands/commands.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 logging
-
-from cliff.lister import Lister
-
-from os_capacity import prometheus
-from os_capacity import utils
-
-
-
-class PrometheusAll(Lister):
- """To be run as node exporter textfile collector."""
- def take_action(self, parsed_args):
- prometheus.print_exporter_data(self.app)
- # TODO(johngarbutt) a total hack!
- return (('fake'), [])
-
-
-class FlavorList(Lister):
- """List all the flavors."""
-
- log = logging.getLogger(__name__)
-
- def take_action(self, parsed_args):
- flavors = utils.get_flavors(self.app)
- return (('UUID', 'Name', 'VCPUs', 'RAM MB', 'DISK GB', 'Extra Specs'),
- flavors)
-
-
-class ListResourcesAll(Lister):
- """List all resource providers, with their resources and servers."""
-
- def take_action(self, parsed_args):
- inventories = utils.get_providers_with_resources_and_servers(self.app)
- return (('Provider Name', 'Resources', 'Severs'), inventories)
-
-
-class ListResourcesGroups(Lister):
- """Lists counts of resource providers with similar inventories."""
-
- def take_action(self, parsed_args):
- groups = utils.group_providers_by_type_with_capacity(self.app)
- return (
- ('Resource Class Groups', 'Total', 'Used', 'Free', 'Flavors'),
- groups)
-
-
-class ListUsagesAll(Lister):
- """List all current resource usages."""
-
- def take_action(self, parsed_args):
- allocations = utils.get_allocations_with_server_info(self.app,
- get_names=True)
- return (
- ('Provider Name', 'Server UUID', 'Resources',
- 'Flavor', 'Days', 'Project', 'User'), allocations)
-
-
-class ListUsagesGroup(Lister):
- """Group usage by specified key (by user or project).
-
- NOTE: The usage days is not complete as it only takes into
- account any currently active servers. Any previously deleted
- servers are not counted.
- """
-
- def get_parser(self, prog_name):
- parser = super(ListUsagesGroup, self).get_parser(prog_name)
- parser.add_argument('group_by', nargs='?', default='user',
- help='Group by user_id or project_id or all',
- choices=['user', 'project', 'all'])
- return parser
-
- def take_action(self, parsed_args):
- usages = utils.group_usage(self.app, parsed_args.group_by)
- sort_key_title = parsed_args.group_by.title()
- return ((sort_key_title, 'Current Usage', 'Usage Days'), usages)
diff --git a/os_capacity/data/__init__.py b/os_capacity/data/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/os_capacity/data/flavors.py b/os_capacity/data/flavors.py
deleted file mode 100644
index 9849725..0000000
--- a/os_capacity/data/flavors.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 collections
-
-
-Flavor = collections.namedtuple(
- "Flavor", ("id", "name", "vcpus", "ram_mb", "disk_gb", "extra_specs"))
-
-
-def get_all(compute_client, include_extra_specs=True):
- response = compute_client.get('/flavors/detail').json()
- raw_flavors = response['flavors']
-
- extra_specs = {}
- if include_extra_specs:
- for flavor in raw_flavors:
- url = '/flavors/%s/os-extra_specs' % flavor['id']
- response = compute_client.get(url).json()
- extra_specs[flavor['id']] = response['extra_specs']
-
- return [Flavor(f['id'], f['name'], f['vcpus'], f['ram'],
- (f['disk'] + f['OS-FLV-EXT-DATA:ephemeral']),
- extra_specs.get(f['id']))
- for f in raw_flavors]
diff --git a/os_capacity/data/resource_provider.py b/os_capacity/data/resource_provider.py
deleted file mode 100644
index 307564a..0000000
--- a/os_capacity/data/resource_provider.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 collections
-
-
-ResourceProvider = collections.namedtuple(
- "ResourceProvider", ("uuid", "name"))
-
-Inventory = collections.namedtuple(
- "Inventory", ("resource_provider_uuid", "resource_class", "total"))
-
-Allocation = collections.namedtuple(
- "Allocation", ("resource_provider_uuid", "consumer_uuid",
- "resources"))
-
-ResourceClassAmount = collections.namedtuple(
- "ResourceClassAmount", ("resource_class", "amount"))
-
-
-def get_all(placement_client):
- response = placement_client.get("/resource_providers").json()
- raw_rps = response['resource_providers']
- return [ResourceProvider(f['uuid'], f['name']) for f in raw_rps]
-
-
-def get_inventories(placement_client, resource_provider):
- uuid = resource_provider.uuid
- url = "/resource_providers/%s/inventories" % uuid
- response = placement_client.get(url).json()
- raw_inventories = response['inventories']
-
- inventories = []
- for resource_class, raw_inventory in raw_inventories.items():
- inventory = Inventory(
- uuid, resource_class, raw_inventory['total'])
- inventories.append(inventory)
-
- return inventories
-
-
-def get_all_inventories(placement_client, resource_providers=None):
- if resource_providers is None:
- resource_providers = get_all(placement_client)
-
- inventories = []
- for resource_provider in resource_providers:
- inventories += get_inventories(placement_client, resource_provider)
-
- return inventories
-
-
-def get_allocations(placement_client, resource_provider):
- allocations = []
- url = "/resource_providers/%s/allocations" % resource_provider.uuid
- response = placement_client.get(url).json()
- raw_allocations = response['allocations']
- for consumer_uuid, raw_allocation in raw_allocations.items():
- raw_resources = raw_allocation['resources']
- resources = []
- for resource_class, amount in raw_resources.items():
- resources.append(ResourceClassAmount(resource_class, amount))
- resources.sort()
-
- allocations.append(Allocation(
- resource_provider.uuid, consumer_uuid, resources))
- return allocations
-
-
-def get_all_allocations(placement_client, resource_providers=None):
- if resource_providers is None:
- resource_providers = get_all(placement_client)
-
- allocations = []
- for resource_provider in resource_providers:
- allocations += get_allocations(placement_client, resource_provider)
-
- return allocations
diff --git a/os_capacity/data/server.py b/os_capacity/data/server.py
deleted file mode 100644
index 4a94942..0000000
--- a/os_capacity/data/server.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 collections
-from datetime import datetime
-
-
-Server = collections.namedtuple(
- "Server", ("uuid", "name", "created",
- "user_id", "project_id", "flavor_id"))
-
-
-def _parse_created(raw_created):
- """Parse a string like 2017-02-14T19:23:58Z"""
- return datetime.strptime(raw_created, "%Y-%m-%dT%H:%M:%SZ")
-
-
-def get(compute_client, uuid):
- url = "/servers/%s" % uuid
- response = compute_client.get(url)
- if not response.ok:
- return None
- raw_server = response.json()['server']
- return Server(
- uuid=raw_server['id'],
- name=raw_server['name'],
- created=_parse_created(raw_server['created']),
- user_id=raw_server['user_id'],
- project_id=raw_server['tenant_id'],
- flavor_id=raw_server['flavor'].get('id'),
- )
diff --git a/os_capacity/data/users.py b/os_capacity/data/users.py
deleted file mode 100644
index f4c8a72..0000000
--- a/os_capacity/data/users.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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.
-
-
-def get_all(identity_client):
- response = identity_client.get("/users").json()
- raw_users = response['users']
- return {u['id']: u['name'] for u in raw_users}
-
-
-def get_all_projects(identity_client):
- response = identity_client.get("/projects").json()
- raw_projects = response['projects']
- return {u['id']: u['name'] for u in raw_projects}
diff --git a/os_capacity/prometheus.py b/os_capacity/prometheus.py
index 58253e9..9e0ac7d 100755
--- a/os_capacity/prometheus.py
+++ b/os_capacity/prometheus.py
@@ -1,8 +1,18 @@
#!/usr/bin/env python3
+# 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 os
import collections
-import json
+import os
import time
import uuid
@@ -174,12 +184,12 @@ def get_host_details(compute_client, placement_client):
counts = capacity_per_flavor.get(flavor.name, {}).values()
total = 0 if not counts else sum(counts)
free_by_flavor_total.add_metric([flavor.name, str(flavor.is_public)], total)
- # print(f'openstack_free_capacity_by_flavor{{flavor="{flavor_name}"}} {total}')
# capacity per host
free_by_flavor_hypervisor = prom_core.GaugeMetricFamily(
"openstack_free_capacity_hypervisor_by_flavor",
- "Free capacity for each hypervisor if you fill remaining space full of each flavor",
+ "Free capacity for each hypervisor if you fill "
+ "remaining space full of each flavor",
labels=["hypervisor", "flavor_name", "az_aggregate", "project_aggregate"],
)
resource_providers, project_to_aggregate = get_resource_provider_info(
@@ -203,7 +213,8 @@ def get_host_details(compute_client, placement_client):
)
free_space_found = True
if not free_space_found:
- # TODO(johngarbutt) allocation candidates only returns some not all candidates!
+ # TODO(johngarbutt) allocation candidates only returns some,
+ # not all candidates!
print(f"# WARNING - no free spaces found for {hostname}")
project_filter_aggregates = prom_core.GaugeMetricFamily(
@@ -214,9 +225,6 @@ def get_host_details(compute_client, placement_client):
for project, names in project_to_aggregate.items():
for name in names:
project_filter_aggregates.add_metric([project, name], 1)
- # print(
- # f'openstack_project_filter_aggregate{{project_id="{project}",aggregate="{name}"}} 1'
- # )
return resource_providers, [
free_by_flavor_total,
free_by_flavor_hypervisor,
@@ -312,10 +320,6 @@ def get_host_usage(resource_providers, placement_client):
return [usage_guage, capacity_guage]
-def print_exporter_data(app):
- print_host_free_details(app.compute_client, app.placement_client)
-
-
class OpenStackCapacityCollector(object):
def __init__(self):
self.conn = openstack.connect()
@@ -346,7 +350,8 @@ def collect(self):
host_time = time.perf_counter()
host_duration = host_time - start_time
print(
- f"1 of 3: host flavor capacity complete for {collect_id} it took {host_duration} seconds"
+ "1 of 3: host flavor capacity complete "
+ f"for {collect_id} it took {host_duration} seconds"
)
if not skip_project_usage:
@@ -354,17 +359,19 @@ def collect(self):
project_time = time.perf_counter()
project_duration = project_time - host_time
print(
- f"2 of 3: project usage complete for {collect_id} it took {project_duration} seconds"
+ "2 of 3: project usage complete "
+ f"for {collect_id} it took {project_duration} seconds"
)
else:
print("2 of 3: skipping project usage")
- if not skip_project_usage:
+ if not skip_host_usage:
guages += get_host_usage(resource_providers, conn.placement)
host_usage_time = time.perf_counter()
host_usage_duration = host_usage_time - project_time
print(
- f"3 of 3: host usage complete for {collect_id} it took {host_usage_duration} seconds"
+ "3 of 3: host usage complete for "
+ f"{collect_id} it took {host_usage_duration} seconds"
)
else:
print("3 of 3: skipping host usage")
@@ -377,7 +384,7 @@ def collect(self):
return guages
-if __name__ == "__main__":
+def main():
kwargs = {
"port": int(os.environ.get("OS_CAPACITY_EXPORTER_PORT", 9000)),
"addr": os.environ.get("OS_CAPACITY_EXPORTER_LISTEN_ADDRESS", "0.0.0.0"),
@@ -388,3 +395,7 @@ def collect(self):
# there must be a better way!
while True:
time.sleep(5000)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/os_capacity/shell.py b/os_capacity/shell.py
deleted file mode 100644
index a78ff9e..0000000
--- a/os_capacity/shell.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 sys
-
-from cliff.app import App
-from cliff.commandmanager import CommandManager
-import os_client_config
-
-import openstack
-
-def get_cloud_config():
- # TODO(johngarbutt) consider passing in argument parser
- return os_client_config.get_config()
-
-
-def get_client(cloud_config, service_type):
- return cloud_config.get_session_client(service_type)
-
-
-class CapacityApp(App):
-
- def __init__(self):
- super(CapacityApp, self).__init__(
- description='OS-Capacity (StackHPC) Command Line Interface (CLI)',
- version='0.1',
- command_manager=CommandManager('os_capacity.commands'),
- deferred_help=True,
- )
-
- def initialize_app(self, argv):
- self.LOG.debug('initialize_app')
-
- conn = openstack.connect()
- self.connection = conn
- self.compute_client = conn.compute
- self.placement_client = conn.placement
- self.identity_client = conn.identity
-
- self.LOG.debug('setup Keystone API REST clients')
-
- def prepare_to_run_command(self, cmd):
- self.LOG.debug('prepare_to_run_command %s', cmd.__class__.__name__)
-
- def clean_up(self, cmd, result, err):
- self.LOG.debug('clean_up %s', cmd.__class__.__name__)
- if err:
- self.LOG.debug('got an error: %s', err)
-
-
-def main(argv=sys.argv[1:]):
- myapp = CapacityApp()
- return myapp.run(argv)
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
diff --git a/os_capacity/tests/unit/fakes.py b/os_capacity/tests/unit/fakes.py
deleted file mode 100644
index a47dd0e..0000000
--- a/os_capacity/tests/unit/fakes.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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.
-
-FLAVOR = {
- 'OS-FLV-DISABLED:disabled': False,
- 'OS-FLV-EXT-DATA:ephemeral': 0,
- 'disk': 30,
- 'id': 'd0e9df0c-34a3-4283-9547-d873e4e86a41',
- 'links': [
- {'href': 'http://example.com:8774/v2.1/flavors/'
- 'd0e9df0c-34a3-4283-9547-d873e4e86a41',
- 'rel': 'self'},
- {'href': 'http://10.60.253.1:8774/flavors/'
- 'd0e9df0c-34a3-4283-9547-d873e4e86a41',
- 'rel': 'bookmark'}],
- 'name': 'compute-GPU',
- 'os-flavor-access:is_public': True,
- 'ram': 2048,
- 'rxtx_factor': 1.0,
- 'swap': '',
- 'vcpus': 8,
-}
-FLAVOR_RESPONSE = {'flavors': [FLAVOR]}
-
-FLAVOR_EXTRA_RESPONSE = {'extra_specs': {"example_key": "example_value"}}
-
-RESOURCE_PROVIDER = {
- 'generation': 6,
- 'name': 'name1',
- 'uuid': '97585d53-67a6-4e9d-9fe7-cd75b331b17b',
- 'links': [
- {'href': '/resource_providers'
- '/97585d53-67a6-4e9d-9fe7-cd75b331b17c',
- 'rel': 'self'},
- {'href': '/resource_providers'
- '/97585d53-67a6-4e9d-9fe7-cd75b331b17c/aggregates',
- 'rel': 'aggregates'},
- {'href': '/resource_providers'
- '/97585d53-67a6-4e9d-9fe7-cd75b331b17c/inventories',
- 'rel': 'inventories'},
- {'href': '/resource_providers'
- '/97585d53-67a6-4e9d-9fe7-cd75b331b17c/usages',
- 'rel': 'usages'}
- ]
-}
-RESOURCE_PROVIDER_RESPONSE = {'resource_providers': [RESOURCE_PROVIDER]}
-
-INVENTORIES = {
- 'DISK_GB': {
- 'allocation_ratio': 1.0,
- 'max_unit': 10,
- 'min_unit': 1,
- 'reserved': 0,
- 'step_size': 1,
- 'total': 10},
- 'MEMORY_MB': {
- 'max_unit': 20,
- 'total': 20},
- 'VCPU': {
- 'max_unit': 30,
- 'total': 30},
-}
-INVENTORIES_RESPONSE = {
- 'inventories': INVENTORIES,
- 'resource_provider_generation': 7,
-}
-
-ALLOCATIONS = {
- 'consumer_uuid': {
- 'resources': {
- 'DISK_GB': 10, 'MEMORY_MB': 20, 'VCPU': 30}
- }
-}
-ALLOCATIONS_RESPONSE = {
- 'allocations': ALLOCATIONS,
- 'resource_provider_generation': 42,
-}
-
-SERVER = {
- "OS-DCF:diskConfig": "AUTO",
- "OS-EXT-AZ:availability_zone": "nova",
- "OS-EXT-SRV-ATTR:host": "compute",
- "OS-EXT-SRV-ATTR:hostname": "new-server-test",
- "OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
- "OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
- "OS-EXT-SRV-ATTR:kernel_id": "",
- "OS-EXT-SRV-ATTR:launch_index": 0,
- "OS-EXT-SRV-ATTR:ramdisk_id": "",
- "OS-EXT-SRV-ATTR:reservation_id": "r-ov3q80zj",
- "OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
- "OS-EXT-SRV-ATTR:user_data": "",
- "OS-EXT-STS:power_state": 1,
- "OS-EXT-STS:task_state": None,
- "OS-EXT-STS:vm_state": "active",
- "OS-SRV-USG:launched_at": "2017-02-14T19:23:59.895661",
- "OS-SRV-USG:terminated_at": None,
- "accessIPv4": "1.2.3.4",
- "accessIPv6": "80fe::",
- "addresses": {
- "private": [
- {
- "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
- "OS-EXT-IPS:type": "fixed",
- "addr": "192.168.0.3",
- "version": 4
- }
- ]
- },
- "config_drive": "",
- "created": "2017-02-14T19:23:58Z",
- "description": "fake description",
- "flavor": {
- "id": "7b46326c-ce48-4e43-8aca-4f5ca00d5f37",
- "ephemeral": 0,
- "extra_specs": {
- "hw:cpu_model": "SandyBridge",
- "hw:mem_page_size": "2048",
- "hw:cpu_policy": "dedicated"
- },
- "original_name": "m1.tiny.specs",
- "ram": 512,
- "swap": 0,
- "vcpus": 1
- },
- "hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
- "host_status": "UP",
- "id": "9168b536-cd40-4630-b43f-b259807c6e87",
- "image": {
- "id": "70a599e0-31e7-49b7-b260-868f441e862b",
- "links": [
- {
- "href": "http://openstack.example.com"
- "/images/70a599e0-31e7-49b7-b260-868f441e862b",
- "rel": "bookmark"
- }
- ]
- },
- "key_name": None,
- "links": [
- {
- "href": "http://openstack.example.com/v2.1/servers"
- "/9168b536-cd40-4630-b43f-b259807c6e87",
- "rel": "self"
- },
- {
- "href": "http://openstack.example.com/servers"
- "/9168b536-cd40-4630-b43f-b259807c6e87",
- "rel": "bookmark"
- }
- ],
- "locked": False,
- "metadata": {
- "My Server Name": "Apache1"
- },
- "name": "new-server-test",
- "os-extended-volumes:volumes_attached": [
- {
- "delete_on_termination": False,
- "id": "volume_id1"
- },
- {
- "delete_on_termination": False,
- "id": "volume_id2"
- }
- ],
- "progress": 0,
- "security_groups": [
- {
- "name": "default"
- }
- ],
- "status": "ACTIVE",
- "tags": [],
- "tenant_id": "6f70656e737461636b20342065766572",
- "updated": "2017-02-14T19:24:00Z",
- "user_id": "fake"
-}
-SERVER_RESPONSE = {'server': SERVER}
diff --git a/os_capacity/tests/unit/test_data.py b/os_capacity/tests/unit/test_data.py
deleted file mode 100644
index 5b1b6a3..0000000
--- a/os_capacity/tests/unit/test_data.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 datetime
-import unittest
-from unittest import mock
-
-from os_capacity.data import flavors
-from os_capacity.data import resource_provider
-from os_capacity.data import server
-from os_capacity.tests.unit import fakes
-
-
-class TestFlavor(unittest.TestCase):
-
- def test_get_all_no_extra_specs(self):
- fake_response = mock.MagicMock()
- fake_response.json.return_value = fakes.FLAVOR_RESPONSE
- compute_client = mock.MagicMock()
- compute_client.get.return_value = fake_response
-
- result = flavors.get_all(compute_client, False)
-
- compute_client.get.assert_called_once_with("/flavors/detail")
- expected_flavors = [flavors.Flavor(fakes.FLAVOR['id'], 'compute-GPU',
- 8, 2048, 30, None)]
- self.assertEqual(expected_flavors, result)
-
- def test_get_all(self):
- fake_response1 = mock.MagicMock()
- fake_response1.json.return_value = fakes.FLAVOR_RESPONSE
- fake_response2 = mock.MagicMock()
- fake_response2.json.return_value = fakes.FLAVOR_EXTRA_RESPONSE
- compute_client = mock.MagicMock()
- compute_client.get.side_effect = [fake_response1, fake_response2]
-
- result = flavors.get_all(compute_client)
-
- compute_client.get.assert_has_calls([
- mock.call("/flavors/detail"),
- mock.call("/flavors/%s/os-extra_specs" % fakes.FLAVOR['id'])])
- expected_flavors = [flavors.Flavor(fakes.FLAVOR['id'], 'compute-GPU',
- 8, 2048, 30,
- {'example_key': 'example_value'})]
- self.assertEqual(expected_flavors, result)
-
-
-class TestResourceProvider(unittest.TestCase):
-
- def test_get_all(self):
- fake_response = mock.MagicMock()
- fake_response.json.return_value = fakes.RESOURCE_PROVIDER_RESPONSE
- placement_client = mock.MagicMock()
- placement_client.get.return_value = fake_response
-
- result = resource_provider.get_all(placement_client)
-
- placement_client.get.assert_called_once_with("/resource_providers")
- self.assertEqual([(fakes.RESOURCE_PROVIDER['uuid'], 'name1')], result)
-
-
-class TestInventory(unittest.TestCase):
-
- def test_get_inventories(self):
- fake_response = mock.MagicMock()
- fake_response.json.return_value = fakes.INVENTORIES_RESPONSE
- client = mock.MagicMock()
- client.get.return_value = fake_response
-
- rp = resource_provider.ResourceProvider("uuid", "name")
-
- result = resource_provider.get_inventories(client, rp)
-
- client.get.assert_called_once_with(
- "/resource_providers/uuid/inventories")
- self.assertEqual(3, len(result))
- disk = resource_provider.Inventory("uuid", "DISK_GB", 10)
- self.assertIn(disk, result)
- mem = resource_provider.Inventory("uuid", "MEMORY_MB", 20)
- self.assertIn(mem, result)
- vcpu = resource_provider.Inventory("uuid", "VCPU", 30)
- self.assertIn(vcpu, result)
-
- @mock.patch.object(resource_provider, 'get_all')
- def test_get_all_inventories(self, mock_get_all):
- fake_response = mock.MagicMock()
- fake_response.json.return_value = fakes.INVENTORIES_RESPONSE
- client = mock.MagicMock()
- client.get.return_value = fake_response
-
- rp1 = resource_provider.ResourceProvider("uuid1", "name1")
- rp2 = resource_provider.ResourceProvider("uuid2", "name2")
- mock_get_all.return_value = [rp1, rp2]
-
- result = resource_provider.get_all_inventories(client)
-
- mock_get_all.assert_called_once_with(client)
- client.get.assert_called_with(
- "/resource_providers/uuid2/inventories")
- self.assertEqual(6, len(result))
- disk1 = resource_provider.Inventory("uuid1", "DISK_GB", 10)
- self.assertIn(disk1, result)
- disk2 = resource_provider.Inventory("uuid2", "DISK_GB", 10)
- self.assertIn(disk2, result)
-
-
-class TestAllocations(unittest.TestCase):
-
- def test_get_allocations(self):
- fake_response = mock.MagicMock()
- fake_response.json.return_value = fakes.ALLOCATIONS_RESPONSE
- client = mock.MagicMock()
- client.get.return_value = fake_response
-
- rp = resource_provider.ResourceProvider("uuid", "name")
-
- result = resource_provider.get_allocations(client, rp)
-
- self.assertEqual(1, len(result))
- expected = resource_provider.Allocation(
- "uuid", "consumer_uuid", [
- resource_provider.ResourceClassAmount("DISK_GB", 10),
- resource_provider.ResourceClassAmount("MEMORY_MB", 20),
- resource_provider.ResourceClassAmount("VCPU", 30)])
- self.assertEqual(expected, result[0])
-
- @mock.patch.object(resource_provider, 'get_all')
- def test_get_all_allocations(self, mock_get_all):
- fake_response = mock.MagicMock()
- fake_response.json.return_value = fakes.ALLOCATIONS_RESPONSE
- client = mock.MagicMock()
- client.get.return_value = fake_response
-
- rp1 = resource_provider.ResourceProvider("uuid1", "name1")
- rp2 = resource_provider.ResourceProvider("uuid2", "name2")
- mock_get_all.return_value = [rp1, rp2]
-
- result = resource_provider.get_all_allocations(client)
-
- self.assertEqual(2, len(result))
- uuid1 = resource_provider.Allocation(
- "uuid1", "consumer_uuid", [
- resource_provider.ResourceClassAmount("DISK_GB", 10),
- resource_provider.ResourceClassAmount("MEMORY_MB", 20),
- resource_provider.ResourceClassAmount("VCPU", 30)])
- self.assertIn(uuid1, result)
- uuid2 = resource_provider.Allocation(
- "uuid1", "consumer_uuid", [
- resource_provider.ResourceClassAmount("DISK_GB", 10),
- resource_provider.ResourceClassAmount("MEMORY_MB", 20),
- resource_provider.ResourceClassAmount("VCPU", 30)])
- self.assertIn(uuid2, result)
-
-
-class TestServer(unittest.TestCase):
- def test_parse_created(self):
- result = server._parse_created("2017-02-14T19:23:58Z")
- expected = datetime.datetime(2017, 2, 14, 19, 23, 58)
- self.assertEqual(expected, result)
-
- def test_get(self):
- mock_response = mock.Mock()
- mock_response.json.return_value = fakes.SERVER_RESPONSE
- compute_client = mock.Mock()
- compute_client.get.return_value = mock_response
-
- uuid = '9168b536-cd40-4630-b43f-b259807c6e87'
- result = server.get(compute_client, uuid)
-
- expected = server.Server(
- uuid=uuid,
- name='new-server-test',
- created=datetime.datetime(2017, 2, 14, 19, 23, 58),
- user_id='fake',
- project_id='6f70656e737461636b20342065766572',
- flavor_id='7b46326c-ce48-4e43-8aca-4f5ca00d5f37')
- self.assertEqual(expected, result)
diff --git a/os_capacity/tests/unit/test_prometheus.py b/os_capacity/tests/unit/test_prometheus.py
new file mode 100644
index 0000000..cc3d687
--- /dev/null
+++ b/os_capacity/tests/unit/test_prometheus.py
@@ -0,0 +1,37 @@
+# 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 unittest
+
+from os_capacity import prometheus
+
+
+class FakeFlavor:
+ def __init__(self, id, name, vcpus, ram, disk, ephemeral, extra_specs):
+ self.id = id
+ self.name = name
+ self.vcpus = vcpus
+ self.ram = ram
+ self.disk = disk
+ self.ephemeral = ephemeral
+ self.extra_specs = extra_specs
+
+
+class TestFlavor(unittest.TestCase):
+ def test_get_placement_request(self):
+ flavor = FakeFlavor(
+ "fake_id", "fake_name", 8, 2048, 30, 0, {"hw:cpu_policy": "dedicated"}
+ )
+ resources, traits = prometheus.get_placement_request(flavor)
+
+ self.assertEqual({"PCPU": 8, "MEMORY_MB": 2048, "DISK_GB": 30}, resources)
+ self.assertEqual([], traits)
diff --git a/os_capacity/tests/unit/test_utils.py b/os_capacity/tests/unit/test_utils.py
deleted file mode 100644
index 466d9af..0000000
--- a/os_capacity/tests/unit/test_utils.py
+++ /dev/null
@@ -1,239 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 datetime
-import unittest
-from unittest import mock
-
-from os_capacity.data import flavors
-from os_capacity.data import resource_provider
-from os_capacity.data import server
-from os_capacity import utils
-
-
-class TestUtils(unittest.TestCase):
-
- @mock.patch.object(flavors, "get_all")
- def test_get_flavors(self, mock_flavors):
- mock_list = [flavors.Flavor(
- id="id", name="name", vcpus=1, ram_mb=2, disk_gb=3,
- extra_specs={})]
- mock_flavors.return_value = mock_list
- app = mock.Mock()
-
- result = utils.get_flavors(app)
-
- mock_flavors.assert_called_once_with(
- app.compute_client, include_extra_specs=True)
- expected_flavors = [('id', 'name', 1, 2, 3, {})]
- self.assertEqual(mock_list, result)
-
- @mock.patch.object(resource_provider, 'get_allocations')
- @mock.patch.object(resource_provider, 'get_inventories')
- @mock.patch.object(resource_provider, 'get_all')
- def test_get_all_inventories_and_usage(self, mock_grp, mock_gi, mock_ga):
- mock_grp.return_value = [
- resource_provider.ResourceProvider('uuid1', 'name1'),
- resource_provider.ResourceProvider('uuid2', 'name2'),
- resource_provider.ResourceProvider('uuid3', 'name3')]
- fake_r = [
- resource_provider.ResourceClassAmount("CUSTOM_FOO", 3),
- resource_provider.ResourceClassAmount("CUSTOM_BAR", 2),
- ]
- mock_ga.side_effect = [
- [resource_provider.Allocation("uuid1", "consumer_uuid1", fake_r)],
- [],
- [resource_provider.Allocation("uuid1", "consumer_uuid2", fake_r)],
- ]
- mock_gi.side_effect = [
- [resource_provider.Inventory("uuid1", "VCPU", 10),
- resource_provider.Inventory("uuid1", "DISK_GB", 5)],
- [],
- [resource_provider.Inventory("uuid1", "VCPU", 10),
- resource_provider.Inventory("uuid1", "DISK_GB", 6)],
- ]
- app = mock.Mock()
-
- result = list(utils.get_providers_with_resources_and_servers(app))
-
- mock_grp.assert_called_once_with(app.placement_client)
- self.assertEqual(3, mock_gi.call_count)
- self.assertEqual(3, mock_ga.call_count)
-
- self.assertEqual([
- ('name1', 'DISK_GB:5, VCPU:10', 'consumer_uuid1'),
- ('name2', '', ''),
- ('name3', 'DISK_GB:6, VCPU:10', 'consumer_uuid2'),
- ], result)
-
- @mock.patch.object(resource_provider, 'get_allocations')
- @mock.patch.object(resource_provider, 'get_inventories')
- @mock.patch.object(resource_provider, 'get_all')
- @mock.patch.object(flavors, 'get_all')
- def test_group_inventories(self, mock_flav, mock_grp, mock_gi, mock_ga):
- mock_flav.return_value = [
- flavors.Flavor(id="id1", name="flavor1",
- vcpus=1, ram_mb=2, disk_gb=3),
- flavors.Flavor(id="id2", name="flavor2",
- vcpus=1, ram_mb=2, disk_gb=30),
- flavors.Flavor(id="id3", name="flavor3",
- vcpus=1, ram_mb=2, disk_gb=3),
- ]
- mock_grp.return_value = [
- resource_provider.ResourceProvider('uuid1', 'name1'),
- resource_provider.ResourceProvider('uuid2', 'name2'),
- resource_provider.ResourceProvider('uuid3', 'name3')]
- fake_r = [
- resource_provider.ResourceClassAmount("VCPU", 1),
- resource_provider.ResourceClassAmount("MEMORY_MB", 2),
- resource_provider.ResourceClassAmount("DISK_GB", 3),
- ]
- mock_ga.side_effect = [
- [resource_provider.Allocation("uuid1", "consumer_uuid1", fake_r)],
- [],
- [resource_provider.Allocation("uuid3", "consumer_uuid2", fake_r)],
- ]
- mock_gi.side_effect = [
- [resource_provider.Inventory("uuid1", "VCPU", 1),
- resource_provider.Inventory("uuid1", "MEMORY_MB", 2),
- resource_provider.Inventory("uuid1", "DISK_GB", 3)],
- [resource_provider.Inventory("uuid1", "VCPU", 1),
- resource_provider.Inventory("uuid1", "MEMORY_MB", 2),
- resource_provider.Inventory("uuid1", "DISK_GB", 30)],
- [resource_provider.Inventory("uuid1", "VCPU", 1),
- resource_provider.Inventory("uuid1", "MEMORY_MB", 2),
- resource_provider.Inventory("uuid1", "DISK_GB", 3)],
- ]
- app = mock.Mock()
-
- result = list(utils.group_providers_by_type_with_capacity(app))
-
- self.assertEqual(2, len(result))
- expected = [
- ('VCPU:1,MEMORY_MB:2,DISK_GB:30', 1, 0, 1, 'flavor2'),
- ('VCPU:1,MEMORY_MB:2,DISK_GB:3', 2, 2, 0, 'flavor1, flavor3')
- ]
- self.assertEqual(expected, result)
-
- @mock.patch.object(server, 'get')
- @mock.patch.object(resource_provider, 'get_all_allocations')
- @mock.patch.object(resource_provider, 'get_all')
- @mock.patch.object(utils, '_get_now')
- def test_get_allocation_list(self, mock_now, mock_rps, mock_allocations,
- mock_server):
- mock_now.return_value = datetime.datetime(2017, 3, 2)
-
- mock_rps.return_value = [
- resource_provider.ResourceProvider("uuid1", "name1"),
- resource_provider.ResourceProvider("uuid2", "name2")
- ]
- mock_allocations.return_value = [
- resource_provider.Allocation(
- "uuid1", "consumer_uuid2", [
- resource_provider.ResourceClassAmount("DISK_GB", 10),
- resource_provider.ResourceClassAmount("MEMORY_MB", 20),
- resource_provider.ResourceClassAmount("VCPU", 30)]),
- resource_provider.Allocation(
- "uuid2", "consumer_uuid1", [
- resource_provider.ResourceClassAmount("DISK_GB", 10),
- resource_provider.ResourceClassAmount("MEMORY_MB", 20),
- resource_provider.ResourceClassAmount("VCPU", 30)]),
- ]
- mock_server.side_effect = [
- server.Server("consumer_uuid2", "name",
- datetime.datetime(2017, 3, 1),
- "user_id", "project_id", "flavor"),
- server.Server("consumer_uuid2", "name",
- datetime.datetime(2017, 3, 1),
- "user_id", "project_id", "flavor"),
- ]
- app = mock.Mock()
-
- result = utils.get_allocations_with_server_info(app)
-
- mock_rps.assert_called_once_with(app.placement_client)
- self.assertEqual(2, len(result))
- expected1 = utils.AllocationList(
- 'name1', 'consumer_uuid2',
- 'DISK_GB:10, MEMORY_MB:20, VCPU:30',
- 'flavor', 1, 'project_id', 'user_id')
- self.assertEqual(expected1, result[0])
- expected2 = utils.AllocationList(
- 'name2', 'consumer_uuid1',
- 'DISK_GB:10, MEMORY_MB:20, VCPU:30',
- 'flavor', 1, 'project_id', 'user_id')
- self.assertEqual(expected2, result[1])
-
- def _setup_fake_allocations(self, mock_get_allocations):
- fake_usage = [
- resource_provider.ResourceClassAmount("DISK_GB", 10),
- resource_provider.ResourceClassAmount("MEMORY_MB", 20),
- resource_provider.ResourceClassAmount("VCPU", 30),
- ]
- mock_get_allocations.return_value = [
- utils.AllocationList(
- 'name1', 'consumer_uuid2', fake_usage,
- 'flavor', 1, 'project_id', 'user_id1'),
- utils.AllocationList(
- 'name2', 'consumer_uuid1', fake_usage,
- 'flavor', 2, 'project_id', 'user_id1'),
- utils.AllocationList(
- 'name2', 'consumer_uuid1', fake_usage,
- 'flavor', 1, 'project_id', 'user_id2'),
- ]
-
- @mock.patch.object(utils, "get_allocations_with_server_info")
- def test_group_usage(self, mock_get_allocations):
- self._setup_fake_allocations(mock_get_allocations)
- app = mock.Mock()
-
- result = utils.group_usage(app)
-
- expected = [
- ('user_id1',
- 'Count:2, DISK_GB:20, MEMORY_MB:40, VCPU:60',
- 'Count:3, DISK_GB:30, MEMORY_MB:60, VCPU:90'),
- ('user_id2',
- 'Count:1, DISK_GB:10, MEMORY_MB:20, VCPU:30',
- 'Count:1, DISK_GB:10, MEMORY_MB:20, VCPU:30'),
- ]
- self.assertEqual(expected, result)
-
- @mock.patch.object(utils, "get_allocations_with_server_info")
- def test_group_usage_project(self, mock_get_allocations):
- self._setup_fake_allocations(mock_get_allocations)
- app = mock.Mock()
-
- result = utils.group_usage(app, "project")
-
- expected = [
- ('project_id',
- 'Count:3, DISK_GB:30, MEMORY_MB:60, VCPU:90',
- 'Count:4, DISK_GB:40, MEMORY_MB:80, VCPU:120'),
- ]
- self.assertEqual(expected, result)
-
- @mock.patch.object(utils, "get_allocations_with_server_info")
- def test_group_usage_all(self, mock_get_allocations):
- self._setup_fake_allocations(mock_get_allocations)
- app = mock.Mock()
-
- result = utils.group_usage(app, "all")
-
- expected = [
- ('(All)',
- 'Count:3, DISK_GB:30, MEMORY_MB:60, VCPU:90',
- 'Count:4, DISK_GB:40, MEMORY_MB:80, VCPU:120'),
- ]
- self.assertEqual(expected, result)
diff --git a/os_capacity/utils.py b/os_capacity/utils.py
deleted file mode 100644
index 32ea515..0000000
--- a/os_capacity/utils.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 collections
-from datetime import datetime
-import os
-
-from os_capacity.data import flavors
-from os_capacity.data import resource_provider
-from os_capacity.data import server as server_data
-from os_capacity.data import users
-
-IGNORE_CUSTOM_RC = 'OS_CAPACITY_IGNORE_CUSTOM_RC' in os.environ
-
-
-def get_flavors(app):
- app.LOG.debug("Getting flavors")
- return flavors.get_all(app.compute_client, include_extra_specs=True)
-
-
-def get_providers_with_resources_and_servers(app):
- resource_providers = resource_provider.get_all(app.placement_client)
-
- for rp in resource_providers:
- inventories = resource_provider.get_inventories(
- app.placement_client, rp)
- allocations = resource_provider.get_allocations(
- app.placement_client, rp)
-
- inventory_texts = ["%s:%s" % (i.resource_class, i.total)
- for i in inventories]
- inventory_texts.sort()
- inventory_text = ", ".join(inventory_texts)
-
- allocation_texts = [a.consumer_uuid for a in allocations]
- allocation_texts.sort()
- allocation_text = ", ".join(allocation_texts)
-
- yield (rp.name, inventory_text, allocation_text)
-
-
-def group_providers_by_type_with_capacity(app):
- # TODO(johngarbutt) this flavor grouping is very ironic specific
- all_flavors = flavors.get_all(app.compute_client)
- grouped_flavors = collections.defaultdict(list)
- for flavor in all_flavors:
- custom_rc = None
- if not IGNORE_CUSTOM_RC:
- for extra_spec in flavor.extra_specs:
- if extra_spec.startswith('resources:CUSTOM'):
- custom_rc = extra_spec.replace('resources:', '')
- break # Assuming a good Ironic setup here
-
- key = (flavor.vcpus, flavor.ram_mb, flavor.disk_gb, custom_rc)
- grouped_flavors[key] += [flavor.name]
-
- all_resource_providers = resource_provider.get_all(app.placement_client)
-
- inventory_counts = collections.defaultdict(int)
- allocation_counts = collections.defaultdict(int)
- for rp in all_resource_providers:
- inventories = resource_provider.get_inventories(
- app.placement_client, rp)
-
- # TODO(johngarbutt) much refinement needed to be general...
- vcpus = 0
- ram_mb = 0
- disk_gb = 0
- custom_rc = None
- for inventory in inventories:
- if "VCPU" in inventory.resource_class:
- vcpus += inventory.total
- if "MEMORY" in inventory.resource_class:
- ram_mb += inventory.total
- if "DISK" in inventory.resource_class:
- disk_gb += inventory.total
- if inventory.resource_class.startswith('CUSTOM_'):
- if not IGNORE_CUSTOM_RC:
- custom_rc = inventory.resource_class # Ironic specific
- key = (vcpus, ram_mb, disk_gb, custom_rc)
-
- inventory_counts[key] += 1
-
- allocations = resource_provider.get_allocations(
- app.placement_client, rp)
- if allocations:
- allocation_counts[key] += 1
-
- for key, inventory_count in inventory_counts.items():
- resources = "VCPU:%s, MEMORY_MB:%s, DISK_GB:%s, %s" % key
- matching_flavors = grouped_flavors[key]
- matching_flavors.sort()
- matching_flavors = ", ".join(matching_flavors)
- total = inventory_count
- used = allocation_counts[key]
- free = total - used
-
- yield (resources, total, used, free, matching_flavors)
-
-
-def _get_now():
- # To make it easy to mock in tests
- return datetime.now()
-
-
-AllocationList = collections.namedtuple(
- "AllocationList", ("resource_provider_name", "consumer_uuid",
- "usage", "flavor_id", "days",
- "project_id", "user_id"))
-
-
-def get_allocations_with_server_info(app, flat_usage=True, get_names=False):
- """Get allocations, add in server and resource provider details."""
- resource_providers = resource_provider.get_all(app.placement_client)
- rp_dict = {rp.uuid: rp.name for rp in resource_providers}
-
- all_allocations = resource_provider.get_all_allocations(
- app.placement_client, resource_providers)
-
- now = _get_now()
-
- allocation_tuples = []
- for allocation in all_allocations:
- rp_name = rp_dict[allocation.resource_provider_uuid]
-
- # TODO(johngarbutt) this is too presentation like for here
- usage = allocation.resources
- if flat_usage:
- usage_amounts = ["%s:%s" % (rca.resource_class, rca.amount)
- for rca in allocation.resources]
- usage_amounts.sort()
- usage = ", ".join(usage_amounts)
-
- server = server_data.get(app.compute_client, allocation.consumer_uuid)
- if server:
- delta = now - server.created
- days_running = delta.days + 1
-
- allocation_tuples.append(AllocationList(
- rp_name, allocation.consumer_uuid, usage,
- server.flavor_id, days_running, server.project_id,
- server.user_id))
-
- allocation_tuples.sort(key=lambda x: (x.project_id, x.user_id,
- x.days * -1, x.flavor_id))
-
- if get_names:
- all_users = users.get_all(app.identity_client)
- all_projects = users.get_all_projects(app.identity_client)
- all_flavors_list = flavors.get_all(app.compute_client,
- include_extra_specs=False)
- all_flavors = {flavor.id: flavor.name for flavor in all_flavors_list}
-
- updated = []
- for allocation in allocation_tuples:
- user_id = all_users.get(allocation.user_id)
- project_id = all_projects.get(allocation.project_id)
- flavor_id = all_flavors.get(allocation.flavor_id)
- updated.append(AllocationList(
- allocation.resource_provider_name,
- allocation.consumer_uuid,
- allocation.usage,
- flavor_id,
- allocation.days,
- project_id,
- user_id))
- allocation_tuples = updated
-
- return allocation_tuples
-
-
-UsageSummary = collections.namedtuple(
- "UsageSummary", ("resource_provider_name", "consumer_uuid",
- "usage", "flavor_id", "days",
- "project_id", "user_id"))
-
-
-def group_usage(app, group_by="user"):
- all_allocations = get_allocations_with_server_info(app, flat_usage=False)
-
- def get_key(allocation):
- if group_by == "user":
- return allocation.user_id
- if group_by == "project":
- return allocation.project_id
- return "(All)"
-
- grouped_allocations = collections.defaultdict(list)
- for allocation in all_allocations:
- grouped_allocations[get_key(allocation)].append(allocation)
-
- all_users = users.get_all(app.identity_client)
- all_projects = users.get_all_projects(app.identity_client)
-
- summary_tuples = []
- for key, group in grouped_allocations.items():
- grouped_usage = collections.defaultdict(int)
- grouped_usage_days = collections.defaultdict(int)
- for allocation in group:
- for rca in allocation.usage:
- grouped_usage[rca.resource_class] += rca.amount
- grouped_usage_days[rca.resource_class] += (
- rca.amount * allocation.days)
- grouped_usage["Count"] += 1
- grouped_usage_days["Count"] += allocation.days
-
- usage_amounts = ["%s:%s" % (resource_class, total)
- for resource_class, total in grouped_usage.items()]
- usage_amounts.sort()
- usage = ", ".join(usage_amounts)
-
- usage_days_amounts = [
- "%s:%s" % (resource_class, total)
- for resource_class, total in grouped_usage_days.items()]
- usage_days_amounts.sort()
- usage_days = ", ".join(usage_days_amounts)
-
- # Resolve id to name, if possible
- key_name = None
- if group_by == "user":
- key_name = all_users.get(key)
- elif group_by == "project":
- key_name = all_projects.get(key)
-
- summary_tuples.append((key_name or key, usage, usage_days))
-
- if group_by == "user" or group_by == "project_id":
- if group_by == "user":
- dimensions = {"user_id": key}
- name_key = "username"
- else:
- dimensions = {"project_id": key}
- name_key = "project_name"
-
- if key_name:
- dimensions[name_key] = key_name
- value_meta = {'usage_summary': usage}
- dimensions['version'] = '2.0'
-
- # Sort my largest current usage first
- summary_tuples.sort(key=lambda x: x[1], reverse=True)
-
- return summary_tuples
diff --git a/requirements.txt b/requirements.txt
index 4e5edae..689a89b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,3 @@
-cliff>=2.8.0 # Apache
-os-client-config>=1.28.0 # Apache-2.0
-pbr>=2.0.0,!=2.1.0 # Apache-2.0
-prometheus-client==0.16.0
-six # MIT
+openstacksdk
+pbr
+prometheus-client
diff --git a/setup.cfg b/setup.cfg
index b1ce8f4..dd98db5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,28 +1,18 @@
[metadata]
-name = kayobe
+name = os_capacity
summary = Deployment of Scientific OpenStack using OpenStack Kolla
description-file =
README.rst
-author = Mark Goddard
-author-email = mark@stackhpc.com
-home-page = https://stackhpc.com
-classifier =
- Environment :: OpenStack
- Intended Audience :: Information Technology
- Intended Audience :: System Administrators
- Operating System :: POSIX :: Linux
- Programming Language :: Python
- Programming Language :: Python :: 2
- Programming Language :: Python :: 2.7
+author = StackHPC
+author-email = johng@stackhpc.com
+url = https://github.com/stackhpc/os-capacity
+python-requires = >=3.9
+license = Apache-2
[files]
packages =
- kayobe
+ os_capacity
-[build_sphinx]
-all-files = 1
-source-dir = doc/source
-build-dir = doc/build
-
-[upload_sphinx]
-upload-dir = doc/build/html
+[entry_points]
+console_scripts=
+ os_capacity = os_capacity.prometheus:main
diff --git a/setup.py b/setup.py
index 61272ae..32792a1 100644
--- a/setup.py
+++ b/setup.py
@@ -1,63 +1,5 @@
#!/usr/bin/env python
-# Copyright (c) 2017 StackHPC Ltd.
-#
-# 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 setuptools
-from setuptools import find_packages
-from setuptools import setup
-
-
-PROJECT = 'os_capacity'
-VERSION = '0.2'
-
-try:
- long_description = open('README.md', 'rt').read()
-except IOError:
- long_description = ''
-
-setup(
- name=PROJECT,
- version=VERSION,
-
- description='OpenStack capacity tooling',
- long_description=long_description,
-
- author='StackHPC',
- author_email='john.garbutt@stackhpc.com',
-
- url='https://github.com/stackhpc/os-capacity',
- download_url='https://github.com/stackhpc/os-capacity/tarball/master',
-
- provides=[],
- install_requires=open('requirements.txt', 'rt').read().splitlines(),
-
- namespace_packages=[],
- packages=find_packages(),
- include_package_data=True,
-
- entry_points={
- 'console_scripts': [
- 'os-capacity = os_capacity.shell:main',
- ],
- 'os_capacity.commands': [
- 'flavor_list = os_capacity.commands.commands:FlavorList',
- 'resources_all = os_capacity.commands.commands:ListResourcesAll',
- 'resources_group = os_capacity.commands'
- '.commands:ListResourcesGroups',
- 'usages_all = os_capacity.commands.commands:ListUsagesAll',
- 'usages_group = os_capacity.commands.commands:ListUsagesGroup',
- 'prometheus = os_capacity.commands.commands:PrometheusAll',
- ],
- },
-)
+setuptools.setup(setup_requires=["pbr"], pbr=True)
diff --git a/tox.ini b/tox.ini
index 87145c5..f46a41f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,30 +1,28 @@
[tox]
minversion = 2.0
-envlist = py39,pep8
+envlist = py3,black,pep8
skipsdist = True
[testenv]
usedevelop = True
-install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/yoga} {opts} {packages}
+install_command = pip install {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
PYTHONWARNINGS=default::DeprecationWarning
- TESTS_DIR=./kayobe/tests/unit/
-deps = -r{toxinidir}/test-requirements.txt
+deps = -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
commands = stestr run {posargs}
-[testenv:pep8]
+[testenv:cover]
+setenv =
+ VIRTUAL_ENV={envdir}
+ PYTHON=coverage run --source azimuth_caas_operator --parallel-mode
commands =
- flake8 {posargs}
-
-[testenv:venv]
-commands = {posargs}
-
-[testenv:docs]
-commands = python setup.py build_sphinx
-
-[testenv:debug]
-commands = oslo_debug_helper {posargs}
+ stestr run {posargs}
+ coverage combine
+ coverage html -d cover
+ coverage xml -o cover/coverage.xml
+ coverage report
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
@@ -32,3 +30,15 @@ show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
+# match black
+max-line-length = 88
+
+[testenv:pep8]
+commands =
+ black {tox_root}
+ flake8 {posargs}
+allowlist_externals = black
+
+[testenv:black]
+commands = black {tox_root} --check
+allowlist_externals = black