Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add os_ratings role for registering resources in Cloudkitty #30

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ This collection includes content for interacting with OpenStack clouds.
- os_openstackclient
- os_openstacksdk
- os_projects
- os_ratings
- os_volumes
88 changes: 88 additions & 0 deletions roles/os_ratings/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
OpenStack Cloudkitty Ratings
============================

This role can be used to register ratings in OpenStack Cloudkitty.

Requirements
------------

The OpenStack Cloudkitty API should be accessible from the target host.

Role Variables
--------------

`os_ratings_venv` is a path to a directory in which to create a
virtual environment.

`os_ratings_upper_constraints_file` is a file or URL containing Python
upper constraints.

`os_ratings_environment` is a dict of environment variables for use with
OpenStack CLI. Default is empty.

`os_ratings_hashmap_field_mappings` is a list of mappings associated with a
field. Each item is a dict with the following fields:
* `service`
* `name`
* `mappings`
The mappings field is a list, where each item is a dict with the following
fields:
* `value`
* `cost`
* `group` (optional)
* `type`

`os_ratings_hashmap_service_mappings` is a list of mappings not associated with
a field. Each item is a dict with the following fields:
* `service`
* `cost`
* `group` (optional)
* `type`

Dependencies
------------

This role depends on the `stackhpc.openstack.os_openstackclient` role.

Example Playbook
----------------

The following playbook registers a Cloudkitty flavor field with two mappings
for different Nova flavors. It also registers a service mapping based on the
size of images stored in Glance.

```
---
- name: Ensure Cloudkitty ratings are registered
hosts: os-client
tasks:
- import_role:
name: stackhpc.openstack.os_ratings
vars:
os_ratings_venv: "~/os-ratings-venv"
os_ratings_environment:
OS_AUTH_URL: "{{ lookup('env', 'OS_AUTH_URL') }}"
...
os_ratings_hashmap_field_mappings:
- service: instance
name: flavor_id
mappings:
- value: small
cost: 1.0
group: instance_uptime_flavor_id
type: flat
- value: large
cost: 2.0
group: instance_uptime_flavor_id
type: flat
os_ratings_hashmap_service_mappings:
- service: image.size
cost: 0.1
group: volume_ceph
type: flat
```
Comment on lines +47 to +83

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this going to be redundant once this gets pushed as the way to configure and run the CloudKitty playbook is with the role via openstack-config & openstack-config.yml.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I like roles to provide an example in their readme. openstack-config is a separate project and you don't need to use it to use this collection or role. I don't think we even link to it from this collection.


Author Information
------------------

- Mark Goddard (<[email protected]>)
43 changes: 43 additions & 0 deletions roles/os_ratings/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
# Path to a directory in which to create a virtualenv.
os_ratings_venv:
# Upper constraints file for installation of Python dependencies.
os_ratings_upper_constraints_file: https://releases.openstack.org/constraints/upper/2023.1

# Environment variables for use with OpenStack CLI.
os_ratings_environment: {}
# Mappings associated with a field.
# Each item is a dict with the following fields:
# * service
# * name
# * mappings
# The mappings field is a list, where each item is a dict with the following fields:
# * value
# * cost
# * group (optional)
# * type
# For example, for per-instance rating:
# - service: instance
# name: flavor_id
# mappings:
# - value: small
# cost: 1.0
# group: instance_uptime_flavor_id
# type: flat
# - value: large
# cost: 2.0
# group: instance_uptime_flavor_id
# type: flat
os_ratings_hashmap_field_mappings: []
# Mappings not associated with a field.
# Each item is a dict with the following fields:
# * service
# * cost
# * group (optional)
# * type
# For example, for image image storage (MB)
# - service: image.size
# cost: 0.1
# group: volume_ceph
# type: flat
os_ratings_hashmap_service_mappings: []
5 changes: 5 additions & 0 deletions roles/os_ratings/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
dependencies:
- role: stackhpc.openstack.os_openstackclient
os_openstackclient_venv: "{{ os_ratings_venv }}"
os_openstackclient_upper_constraints_file: "{{ os_ratings_upper_constraints_file | default(None) }}"
40 changes: 40 additions & 0 deletions roles/os_ratings/tasks/field-mappings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# Task file for a single field and its mappings.

- name: Create hashmap field
ansible.builtin.command: >
{{ openstack }} rating hashmap field create {{ service_id }} {{ field.name }}
when: field.name not in fields | map(attribute='Name') | list
changed_when: true

# List again to get ID of created mapping.
- name: List hashmap fields
ansible.builtin.command: >
{{ openstack }} rating hashmap field list -f json {{ service_id }}
register: hashmap_field
changed_when: false

- name: List hashmap field mappings
vars:
field_id: "{{ (hashmap_field.stdout | from_json | selectattr('Name', 'equalto', field.name) | first)['Field ID'] }}"
ansible.builtin.command: >
{{ openstack }} rating hashmap mapping list -f json --field-id {{ field_id }}
register: hashmap_mappings
changed_when: false

- name: Create hashmap field mappings
vars:
field_id: "{{ (hashmap_field.stdout | from_json | selectattr('Name', 'equalto', field.name) | first)['Field ID'] }}"
group_id: >-
{{ (hashmap_groups.stdout | from_json | selectattr('Name', 'equalto', item.group) | first)['Group ID'] | default('') if item.group is defined else '' }}
ansible.builtin.command: >
{{ openstack }} rating hashmap mapping create
{{ item.cost }}
--field-id {{ field_id }}
--value {{ item.value }}
{% if group_id | length > 0 %}--group-id {{ group_id }}{% endif %}
--type {{ item.type }}
loop: "{{ field.mappings }}"
# Condition could be better, but should work with current values.
when: item.value not in (hashmap_mappings.stdout | from_json | map(attribute='Value') | list)
changed_when: true
22 changes: 22 additions & 0 deletions roles/os_ratings/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
- name: Ensure Cloudkitty client is installed # noqa package-latest
ansible.builtin.pip:
name:
- python-cloudkittyclient
state: latest
extra_args: "{% if os_ratings_upper_constraints_file %}-c {{ os_ratings_upper_constraints_file }}{% endif %}"
virtualenv: "{{ os_ratings_venv }}"
run_once: true

- name: Set a fact about the Ansible python interpreter
ansible.builtin.set_fact:
old_ansible_python_interpreter: "{{ ansible_python_interpreter | default('/usr/bin/python3') }}"

- name: Import ratings.yml
ansible.builtin.import_tasks: ratings.yml
vars:
ansible_python_interpreter: "{{ os_ratings_venv ~ '/bin/python' if os_ratings_venv != None else old_ansible_python_interpreter }}"
openstack: "{{ os_ratings_venv ~ '/bin/' if os_ratings_venv else '' }}openstack"
os_ratings_hashmap_field_mapping_services: "{{ os_ratings_hashmap_field_mappings | map(attribute='service') | list }}"
os_ratings_hashmap_service_mapping_services: "{{ os_ratings_hashmap_service_mappings | map(attribute='service') | list }}"
environment: "{{ os_ratings_environment }}"
110 changes: 110 additions & 0 deletions roles/os_ratings/tasks/ratings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
- name: List modules
ansible.builtin.command: >
{{ openstack }} rating module list -f json
register: modules
changed_when: false

- name: Enable hashmap module
ansible.builtin.command: >
{{ openstack }} rating module enable hashmap
when: not (modules.stdout | from_json | selectattr('Module', 'equalto', 'hashmap') | first)['Enabled'] | bool
changed_when: true

- name: List hashmap services
ansible.builtin.command: >
{{ openstack }} rating hashmap service list -f json
register: hashmap_services
changed_when: false

- name: Create hashmap services
vars:
existing_services: "{{ hashmap_services.stdout | from_json | map(attribute='Name') | list }}"
ansible.builtin.command: >
{{ openstack }} rating hashmap service create {{ item }}
loop: "{{ (os_ratings_hashmap_field_mapping_services + os_ratings_hashmap_service_mapping_services) | unique | list }}"
when: item not in existing_services
changed_when: true

- name: List hashmap groups
ansible.builtin.command: >
{{ openstack }} rating hashmap group list -f json
register: hashmap_groups
changed_when: false

- name: Create hashmap groups
vars:
existing_groups: "{{ hashmap_groups.stdout | from_json | map(attribute='Name') | list }}"
field_mapping_groups: "{{ query('subelements', os_ratings_hashmap_field_mappings, 'mappings') | map(attribute='1.group') | select('defined') | list }}"
service_mapping_groups: "{{ os_ratings_hashmap_service_mappings | map(attribute='group') | select('defined') | list }}"
ansible.builtin.command: >
{{ openstack }} rating hashmap group create {{ item }}
loop: "{{ (field_mapping_groups + service_mapping_groups) | unique | list }}"
when:
- item is not none and item | length > 0
- item not in existing_groups
changed_when: true

# List again to get IDs of created services.
- name: List hashmap services
ansible.builtin.command: >
{{ openstack }} rating hashmap service list -f json
register: hashmap_services
changed_when: false

# List again to get IDs of created groups.
- name: List hashmap groups
ansible.builtin.command: >
{{ openstack }} rating hashmap group list -f json
register: hashmap_groups
changed_when: false

- name: List hashmap fields
vars:
service_id: "{{ (hashmap_services.stdout | from_json | selectattr('Name', 'equalto', item) | first)['Service ID'] }}"
ansible.builtin.command: >
{{ openstack }} rating hashmap field list {{ service_id }} -f json
loop: "{{ os_ratings_hashmap_field_mapping_services }}"
register: hashmap_fields
changed_when: false

# Field mappings

- name: Include field mappings
ansible.builtin.include_tasks: field-mappings.yml
vars:
fields_result: "{{ hashmap_fields.results | selectattr('item', 'equalto', field.service) | first }}"
fields: "{{ fields_result.stdout | from_json }}"
service_id: "{{ (hashmap_services.stdout | from_json | selectattr('Name', 'equalto', field.service) | first)['Service ID'] }}"
loop: "{{ os_ratings_hashmap_field_mappings }}"
loop_control:
loop_var: field

# Service mappings

- name: List hashmap service mappings
vars:
service_id: "{{ (hashmap_services.stdout | from_json | selectattr('Name', 'equalto', item) | first)['Service ID'] }}"
ansible.builtin.command: >
{{ openstack }} rating hashmap mapping list -f json --service-id {{ service_id }}
loop: "{{ os_ratings_hashmap_service_mapping_services }}"
register: hashmap_mappings
changed_when: false

- name: Create hashmap service mappings
vars:
mappings_result: "{{ hashmap_mappings.results | selectattr('item', 'equalto', item.service) | first }}"
mappings: "{{ mappings_result.stdout | from_json }}"
service_id: "{{ (hashmap_services.stdout | from_json | selectattr('Name', 'equalto', item.service) | first)['Service ID'] }}"
group_id: "{{ (hashmap_groups.stdout | from_json | selectattr('Name', 'equalto', item.group) | first)['Group ID'] | default('') if item.group is defined else
'' }}"
ansible.builtin.command: >
{{ openstack }} rating hashmap mapping create
{{ item.cost }}
--service-id {{ service_id }}
{% if group_id | length > 0 %}--group-id {{ group_id }}{% endif %}
--type {{ item.type }}
loop: "{{ os_ratings_hashmap_service_mappings }}"
# Condition could be better, but should work with current values.
when: mappings | length == 0
changed_when: true