mirror of
https://opendev.org/openstack/ansible-collections-openstack.git
synced 2026-03-27 05:53:02 +00:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bb8312171 | ||
|
|
b3ac841442 | ||
|
|
37c52c321d | ||
|
|
6edc70f965 | ||
|
|
cc8cd08c03 | ||
|
|
b572bf8ae9 | ||
|
|
b3e0d610ea | ||
|
|
291e8b8640 | ||
|
|
f70a50e363 | ||
|
|
befcc4353d | ||
|
|
8a91352a38 | ||
|
|
1d7fd25ac0 | ||
|
|
44fa06cba1 | ||
|
|
26bc8a0666 | ||
|
|
19d0562551 | ||
|
|
07c3ed0c17 | ||
|
|
8708167b5f | ||
|
|
a9565779b5 | ||
|
|
5c2069c47d | ||
|
|
583df2a8a9 | ||
|
|
0f532d10f3 | ||
|
|
87858ab976 | ||
|
|
09c3e4bdc9 | ||
|
|
ebffbe4fe8 | ||
|
|
cbcfce2e23 | ||
|
|
406558dae9 | ||
|
|
c8d89f81a5 | ||
|
|
c0e1f56894 | ||
|
|
a031968f80 | ||
|
|
2e78559cc1 | ||
|
|
bf939a4ce0 | ||
|
|
24d6f36602 | ||
|
|
dd9cdde3d8 | ||
|
|
da4a68c188 | ||
|
|
a7a190f3c0 | ||
|
|
ce73c9db34 | ||
|
|
0e102b1411 | ||
|
|
9f58d54721 | ||
|
|
79d7827d17 | ||
|
|
ba9aa9967f | ||
|
|
617e8fb552 | ||
|
|
5abf89d805 | ||
|
|
0599d05103 | ||
|
|
9c28af7d12 | ||
|
|
bcb5d18492 | ||
|
|
7aa626377b | ||
|
|
0a7889b9a2 | ||
|
|
a1b920742f | ||
|
|
bd55e1f905 | ||
|
|
20329c0329 | ||
|
|
bdf472a53f | ||
|
|
b7fb23b097 | ||
|
|
6ed02eff2d | ||
|
|
cb396cf03d | ||
|
|
20c2633ea3 | ||
|
|
6569e07023 | ||
|
|
031475d42e | ||
|
|
5e2ab3d8c3 | ||
|
|
f3b12fed68 | ||
|
|
39a627d4a0 | ||
|
|
4eb7c43539 | ||
|
|
8c6d1041fa | ||
|
|
26530ac97b | ||
|
|
b3e07a1864 | ||
|
|
94933250e8 | ||
|
|
1582956dcd | ||
|
|
b1952e0c4b | ||
|
|
9cd6d2f69a | ||
|
|
86a57498e8 | ||
|
|
15f73aa72e | ||
|
|
22a7f516f3 | ||
|
|
de54be5ecd | ||
|
|
3f1a693bd6 | ||
|
|
8e87ad651f | ||
|
|
b2f3cf3210 | ||
|
|
11c7cd23f8 | ||
|
|
59d0e4c3a4 | ||
|
|
014665ddac | ||
|
|
21b70f6b9b | ||
|
|
ecaff2a798 | ||
|
|
02e9e87964 | ||
|
|
980536c32e | ||
|
|
a9a0d23441 | ||
|
|
011515de2d | ||
|
|
4292a00f75 | ||
|
|
bbefa8c156 | ||
|
|
b023aa337a | ||
|
|
c13f02fd54 | ||
|
|
ae4e7f3c06 | ||
|
|
2d554d1e22 | ||
|
|
770b283593 | ||
|
|
42921c6d9f | ||
|
|
292aabb477 | ||
|
|
3a08a9c07c | ||
|
|
0e370b2c51 | ||
|
|
0441403c42 | ||
|
|
4160888887 | ||
|
|
07374a1f0d | ||
|
|
8a395a04cf | ||
|
|
441a61fd8c | ||
|
|
c05b1fdbaf | ||
|
|
9cd92208d6 | ||
|
|
5ef192f1f2 | ||
|
|
aed60716ee | ||
|
|
938b90ea19 | ||
|
|
ce853a8f9f | ||
|
|
9911c7f93a | ||
|
|
8c890e656b | ||
|
|
b839f9e25d |
601
.zuul.yaml
601
.zuul.yaml
@@ -1,16 +1,28 @@
|
||||
# yamllint disable
|
||||
---
|
||||
# Keep this file in sync between branches to avoid issues e.g. with job scheduling. Zuul CI will search in master branch
|
||||
# first when collecting job variants during job freeze which can have unwanted side effects. For example, when parent
|
||||
# job *-base has been changed in stable/1.0.0 branch, Zuul could still use *-base variants from master branch during job
|
||||
# freeze on child jobs such as *-ussuri-ansible-2.11 etc.
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack
|
||||
name: ansible-collections-openstack-functional-devstack-base
|
||||
parent: openstacksdk-functional-devstack
|
||||
# Do not restrict branches in base jobs because else Zuul would not find a matching
|
||||
# parent job variant during job freeze when child jobs are on other branches.
|
||||
post-run: ci/playbooks/postlog.yaml
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk with latest ansible release
|
||||
Run openstack collections functional tests against a devstack
|
||||
# Do not set job.override-checkout or job.required-projects.override-checkout in base job because
|
||||
# else Zuul will use this branch when matching variants for parent jobs during job freeze
|
||||
required-projects:
|
||||
- openstack/ansible-collections-openstack
|
||||
- openstack/designate
|
||||
# openstack/devstack is required through parent job openstacksdk-functional-devstack
|
||||
# openstack/os-client-config is required through parent job openstacksdk-functional-devstack
|
||||
# openstack/openstacksdk is required through parent job openstacksdk-functional-devstack
|
||||
irrelevant-files: &ignore_files
|
||||
- changelogs/.*
|
||||
- galaxy.*
|
||||
- COPYING
|
||||
- docs/.*
|
||||
- .*\.md
|
||||
@@ -18,6 +30,7 @@
|
||||
- tools/run-ansible-sanity.sh
|
||||
- tests/sanity/.*
|
||||
- contrib/.*
|
||||
- .zuul.yaml
|
||||
vars:
|
||||
zuul_work_dir: src/opendev.org/openstack/ansible-collections-openstack
|
||||
tox_envlist: ansible
|
||||
@@ -27,19 +40,32 @@
|
||||
designate: https://opendev.org/openstack/designate
|
||||
devstack_services:
|
||||
designate: true
|
||||
neutron-dns: true
|
||||
zuul_copy_output:
|
||||
'{{ devstack_log_dir }}/test_output.log': 'logs'
|
||||
extensions_to_txt:
|
||||
log: true
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-octavia
|
||||
parent: ansible-collections-openstack-functional-devstack
|
||||
name: ansible-collections-openstack-functional-devstack
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
with Octavia plugin enabled, using releases of openstacksdk and latest
|
||||
ansible release. Run it only on Load Balancer changes.
|
||||
using master of openstacksdk with latest ansible release
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-octavia-base
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
# Do not restrict branches in base jobs because else Zuul would not find a matching
|
||||
# parent job variant during job freeze when child jobs are on other branches.
|
||||
description: |
|
||||
Run openstack collections functional tests against a devstack with Octavia plugin enabled
|
||||
pre-run: ci/playbooks/get_amphora_tarball.yaml
|
||||
# Do not set job.override-checkout or job.required-projects.override-checkout in base job because
|
||||
# else Zuul will use this branch when matching variants for parent jobs during job freeze
|
||||
required-projects:
|
||||
- openstack/octavia
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
files:
|
||||
- ^ci/roles/loadbalancer/.*$
|
||||
- ^plugins/modules/lb_health_monitor.py
|
||||
@@ -48,12 +74,14 @@
|
||||
- ^plugins/modules/lb_pool.py
|
||||
- ^plugins/modules/loadbalancer.py
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
configure_swap_size: 8192
|
||||
tox_install_siblings: false
|
||||
devstack_plugins:
|
||||
designate: https://opendev.org/openstack/designate
|
||||
octavia: https://opendev.org/openstack/octavia
|
||||
devstack_services:
|
||||
designate: true
|
||||
neutron-dns: true
|
||||
octavia: true
|
||||
o-api: true
|
||||
o-cw: true
|
||||
@@ -65,22 +93,75 @@
|
||||
OCTAVIA_AMP_IMAGE_NAME: "test-only-amphora-x64-haproxy-ubuntu-bionic"
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-releases
|
||||
parent: ansible-collections-openstack-functional-devstack
|
||||
name: ansible-collections-openstack-functional-devstack-octavia
|
||||
parent: ansible-collections-openstack-functional-devstack-octavia-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using releases of openstacksdk and latest ansible release
|
||||
with Octavia plugin enabled, using latest releases of openstacksdk
|
||||
and latest ansible release. Run it only on Load Balancer changes.
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-octavia
|
||||
parent: ansible-collections-openstack-functional-devstack-octavia-base
|
||||
branches: stable/1.0.0
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
with Octavia plugin enabled, using 0.*.* releases of openstacksdk
|
||||
and latest ansible release. Run it only on Load Balancer changes.
|
||||
required-projects:
|
||||
- # Choose parent devstack job from master branch instead of non-existing stable/1.0.0 branch
|
||||
name: openstack/devstack
|
||||
override-checkout: master
|
||||
- # Choose parent devstack job from master branch instead of non-existing stable/1.0.0 branch
|
||||
name: openstack/openstacksdk
|
||||
override-checkout: master
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-0.x.x.txt'
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-releases
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using latest releases of openstacksdk and latest ansible release
|
||||
# OpenStack SDK 1.*.* has not been released to PyPI yet
|
||||
voting: false
|
||||
vars:
|
||||
# Uncomment once OpenStack SDK 1.*.* has been released to PyPI
|
||||
# tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-1.x.x.txt'
|
||||
tox_install_siblings: false
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-releases
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: stable/1.0.0
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using 0.*.* releases of openstacksdk and latest ansible release
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.9
|
||||
- # Choose parent devstack job from master branch instead of non-existing stable/1.0.0 branch
|
||||
name: openstack/devstack
|
||||
override-checkout: master
|
||||
- # Choose parent devstack job from master branch instead of non-existing stable/1.0.0 branch
|
||||
name: openstack/openstacksdk
|
||||
override-checkout: master
|
||||
vars:
|
||||
tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-0.x.x.txt'
|
||||
tox_install_siblings: false
|
||||
|
||||
# Job with Ansible 2.9 for checking backward compatibility
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
parent: ansible-collections-openstack-functional-devstack
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and stable 2.9 branch of ansible
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.9
|
||||
@@ -88,20 +169,94 @@
|
||||
tox_envlist: ansible-2.9
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: stable/1.0.0
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and stable 2.11 branch of ansible
|
||||
using 0.*.* releases of openstacksdk and stable 2.9 branch of ansible
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.9
|
||||
- # Choose parent devstack job from master branch instead of non-existing stable/1.0.0 branch
|
||||
name: openstack/devstack
|
||||
override-checkout: master
|
||||
- name: openstack/openstacksdk
|
||||
# Yoga has the latest SDK release of the 0.*.* series atm
|
||||
override-checkout: stable/yoga
|
||||
vars:
|
||||
tox_envlist: ansible-2.9
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and stable 2.12 branch of ansible
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: stable/1.0.0
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using 0.*.* releases of openstacksdk and stable 2.12 branch of ansible
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
- # Choose parent devstack job from master branch instead of non-existing stable/1.0.0 branch
|
||||
name: openstack/devstack
|
||||
override-checkout: master
|
||||
- name: openstack/openstacksdk
|
||||
# Yoga has the latest SDK release of the 0.*.* series atm
|
||||
override-checkout: stable/yoga
|
||||
vars:
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and stable 2.12 branch of ansible
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.12
|
||||
vars:
|
||||
tox_envlist: ansible-2.12
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: stable/1.0.0
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using 0.*.* releases of openstacksdk and stable 2.12 branch of ansible
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.12
|
||||
- # Choose parent devstack job from master branch instead of non-existing stable/1.0.0 branch
|
||||
name: openstack/devstack
|
||||
override-checkout: master
|
||||
- name: openstack/openstacksdk
|
||||
# Yoga has the latest SDK release of the 0.*.* series atm
|
||||
override-checkout: stable/yoga
|
||||
vars:
|
||||
tox_envlist: ansible-2.12
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
parent: ansible-collections-openstack-functional-devstack
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
branches: master
|
||||
description: |
|
||||
Run openstack collections functional tests against a master devstack
|
||||
using master of openstacksdk and devel branch of ansible
|
||||
@@ -109,185 +264,236 @@
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: devel
|
||||
vars:
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
# Stable branches tests
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-wallaby-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
name: ansible-collections-openstack-functional-devstack-xena-ansible-2.12
|
||||
# Do not inherit from any parent job which does not run for branch stable/1.0.0 because Zuul would dismiss this job
|
||||
# when collecting parent job variants. For example, when job.branches is set to master in a parent job, then Zuul
|
||||
# will not match that job when it collects job variants.
|
||||
#
|
||||
# Do not inherit from any parent job which sets job.required-projects.override-checkout on openstack/devstack
|
||||
# because Zuul would use that git ref instead of stable branch defined below to checkout projects of parent devstack
|
||||
# jobs when collecting variants for parent jobs.
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
description: |
|
||||
Run openstack collections functional tests against a wallaby devstack
|
||||
using wallaby brach of openstacksdk and stable 2.11 branch of ansible
|
||||
voting: true
|
||||
override-checkout: stable/wallaby
|
||||
Run openstack collections functional tests against a xena devstack
|
||||
using xena branch of openstacksdk and stable 2.12 branch of ansible
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/xena
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
override-checkout: stable-2.12
|
||||
- # Choose parent devstack job from stable/1.0.0 branch instead of non-existing stable/xena branch
|
||||
name: openstack/ansible-collections-openstack
|
||||
override-checkout: stable/1.0.0
|
||||
- # Choose parent devstack job from stable/xena branch
|
||||
name: openstack/devstack
|
||||
override-checkout: stable/xena
|
||||
- name: openstack/openstacksdk
|
||||
override-checkout: stable/xena
|
||||
vars:
|
||||
tox_envlist: ansible-2.12
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-wallaby-ansible-2.12
|
||||
# Do not inherit from any parent job which does not run for branch stable/1.0.0 because Zuul would dismiss this job
|
||||
# when collecting parent job variants. For example, when job.branches is set to master in a parent job, then Zuul
|
||||
# will not match that job when it collects job variants.
|
||||
#
|
||||
# Do not inherit from any parent job which sets job.required-projects.override-checkout on openstack/devstack
|
||||
# because Zuul would use that git ref instead of stable branch defined below to checkout projects of parent devstack
|
||||
# jobs when collecting variants for parent jobs.
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
description: |
|
||||
Run openstack collections functional tests against a wallaby devstack
|
||||
using wallaby branch of openstacksdk and stable 2.12 branch of ansible
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/wallaby
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.12
|
||||
- # Choose parent devstack job from stable/1.0.0 branch instead of non-existing stable/wallaby branch
|
||||
name: openstack/ansible-collections-openstack
|
||||
override-checkout: stable/1.0.0
|
||||
- # Choose parent devstack job from stable/wallaby branch
|
||||
name: openstack/devstack
|
||||
override-checkout: stable/wallaby
|
||||
- name: openstack/openstacksdk
|
||||
override-checkout: stable/wallaby
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
tox_envlist: ansible-2.12
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-victoria-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
name: ansible-collections-openstack-functional-devstack-victoria-ansible-2.12
|
||||
# Do not inherit from any parent job which does not run for branch stable/1.0.0 because Zuul would dismiss this job
|
||||
# when collecting parent job variants. For example, when job.branches is set to master in a parent job, then Zuul
|
||||
# will not match that job when it collects job variants.
|
||||
#
|
||||
# Do not inherit from any parent job which sets job.required-projects.override-checkout on openstack/devstack
|
||||
# because Zuul would use that git ref instead of stable branch defined below to checkout projects of parent devstack
|
||||
# jobs when collecting variants for parent jobs.
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
description: |
|
||||
Run openstack collections functional tests against a victoria devstack
|
||||
using victoria brach of openstacksdk and stable 2.11 branch of ansible
|
||||
voting: true
|
||||
using victoria branch of openstacksdk and stable 2.12 branch of ansible
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/victoria
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
override-checkout: stable-2.12
|
||||
- # Choose parent devstack job from stable/1.0.0 branch instead of non-existing stable/victoria branch
|
||||
name: openstack/ansible-collections-openstack
|
||||
override-checkout: stable/1.0.0
|
||||
- # Choose parent devstack job from stable/victoria branch
|
||||
name: openstack/devstack
|
||||
override-checkout: stable/victoria
|
||||
- name: openstack/openstacksdk
|
||||
override-checkout: stable/victoria
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
|
||||
tox_envlist: ansible-2.12
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-ussuri-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
# Do not inherit from any parent job which does not run for branch stable/1.0.0 because Zuul would dismiss this job
|
||||
# when collecting parent job variants. For example, when job.branches is set to master in a parent job, then Zuul
|
||||
# will not match that job when it collects job variants.
|
||||
#
|
||||
# Do not inherit from any parent job which sets job.required-projects.override-checkout on openstack/devstack
|
||||
# because Zuul would use that git ref instead of stable branch defined below to checkout projects of parent devstack
|
||||
# jobs when collecting variants for parent jobs.
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
description: |
|
||||
Run openstack collections functional tests against a ussuri devstack
|
||||
using ussuri brach of openstacksdk and stable 2.11 branch of ansible
|
||||
voting: true
|
||||
using ussuri branch of openstacksdk and stable 2.11 branch of ansible
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/ussuri
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
- # Choose parent devstack job from stable/1.0.0 branch instead of non-existing stable/ussuri branch
|
||||
name: openstack/ansible-collections-openstack
|
||||
override-checkout: stable/1.0.0
|
||||
- # Choose parent devstack job from stable/ussuri branch
|
||||
name: openstack/devstack
|
||||
override-checkout: stable/ussuri
|
||||
- name: openstack/openstacksdk
|
||||
override-checkout: stable/ussuri
|
||||
- name: openstack/os-client-config
|
||||
override-checkout: stable/ussuri
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-train-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
# Do not inherit from any parent job which does not run for branch stable/1.0.0 because Zuul would dismiss this job
|
||||
# when collecting parent job variants. For example, when job.branches is set to master in a parent job, then Zuul
|
||||
# will not match that job when it collects job variants.
|
||||
#
|
||||
# Do not inherit from any parent job which sets job.required-projects.override-checkout on openstack/devstack
|
||||
# because Zuul would use that git ref instead of stable branch defined below to checkout projects of parent devstack
|
||||
# jobs when collecting variants for parent jobs.
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
description: |
|
||||
Run openstack collections functional tests against a train devstack
|
||||
using train brach of openstacksdk and stable 2.11 branch of ansible
|
||||
using train branch of openstacksdk and stable 2.11 branch of ansible
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/train
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
- # Choose parent devstack job from stable/1.0.0 branch instead of non-existing stable/train branch
|
||||
name: openstack/ansible-collections-openstack
|
||||
override-checkout: stable/1.0.0
|
||||
- # Choose parent devstack job from stable/train branch
|
||||
name: openstack/devstack
|
||||
override-checkout: stable/train
|
||||
- name: openstack/openstacksdk
|
||||
override-checkout: stable/train
|
||||
- name: openstack/os-client-config
|
||||
override-checkout: stable/train
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-queens-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
# Do not inherit from any parent job which does not run for branch stable/1.0.0 because Zuul would dismiss this job
|
||||
# when collecting parent job variants. For example, when job.branches is set to master in a parent job, then Zuul
|
||||
# will not match that job when it collects job variants.
|
||||
#
|
||||
# Do not inherit from any parent job which sets job.required-projects.override-checkout on openstack/devstack
|
||||
# because Zuul would use that git ref instead of stable branch defined below to checkout projects of parent devstack
|
||||
# jobs when collecting variants for parent jobs.
|
||||
parent: ansible-collections-openstack-functional-devstack-base
|
||||
description: |
|
||||
Run openstack collections functional tests against a queens devstack
|
||||
using master branch of openstacksdk and stable 2.11 branch of ansible
|
||||
voting: true
|
||||
using train branch of openstacksdk and stable 2.11 branch of ansible
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/queens
|
||||
# job.override-checkout will not override job.required-projects.override-checkout in parent jobs
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
- name: openstack/openstacksdk
|
||||
# Run queens with highest possible py2 version of SDK
|
||||
override-checkout: stable/train
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-queens-ansible-devel
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
description: |
|
||||
Run openstack collections functional tests against a queens devstack
|
||||
using master branch of openstacksdk and devel branch of ansible
|
||||
voting: false
|
||||
override-checkout: stable/queens
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: devel
|
||||
- # Choose parent devstack job from stable/1.0.0 branch instead of non-existing stable/queens branch
|
||||
name: openstack/ansible-collections-openstack
|
||||
override-checkout: stable/1.0.0
|
||||
- # Choose parent devstack job from stable/queens branch
|
||||
name: openstack/devstack
|
||||
override-checkout: stable/queens
|
||||
- name: openstack/openstacksdk
|
||||
# Run queens with highest possible py2 version of SDK
|
||||
override-checkout: stable/train
|
||||
vars:
|
||||
tox_envlist: ansible-2.11
|
||||
|
||||
# Experimental pipeline jobs
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-stein-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
description: |
|
||||
Run openstack collections functional tests against a stein devstack
|
||||
using stein brach of openstacksdk and stable 2.11 branch of ansible
|
||||
voting: true
|
||||
override-checkout: stable/stein
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
- name: openstack/openstacksdk
|
||||
override-checkout: stable/stein
|
||||
- name: openstack/os-client-config
|
||||
override-checkout: stable/stein
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-functional-devstack-rocky-ansible-2.11
|
||||
parent: ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
description: |
|
||||
Run openstack collections functional tests against a rocky devstack
|
||||
using rocky brach of openstacksdk and stable 2.11 branch of ansible
|
||||
voting: true
|
||||
override-checkout: stable/rocky
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
- name: openstack/openstacksdk
|
||||
override-checkout: stable/rocky
|
||||
- name: openstack/os-client-config
|
||||
override-checkout: stable/rocky
|
||||
- name: openstack/shade
|
||||
override-checkout: stable/rocky
|
||||
|
||||
vars:
|
||||
tox_envlist: ansible
|
||||
|
||||
# Linters
|
||||
- job:
|
||||
name: openstack-tox-linters-ansible-devel
|
||||
name: openstack-tox-linters-ansible
|
||||
parent: openstack-tox-linters
|
||||
nodeset: ubuntu-bionic
|
||||
nodeset: ubuntu-focal
|
||||
description: |
|
||||
Run openstack collections linter tests using the devel branch of ansible
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: devel
|
||||
vars:
|
||||
# override tox_constraints_file from parent job
|
||||
tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-none.txt'
|
||||
tox_envlist: linters
|
||||
tox_install_siblings: true
|
||||
|
||||
- job:
|
||||
name: openstack-tox-linters-ansible-devel
|
||||
parent: openstack-tox-linters-ansible
|
||||
description: |
|
||||
Run openstack collections linter tests using the devel branch of ansible
|
||||
# non-voting because we can't prevent ansible devel from breaking us
|
||||
voting: false
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: devel
|
||||
vars:
|
||||
tox_envlist: linters-2.11
|
||||
python_version: 3.8
|
||||
bindep_profile: test py38
|
||||
|
||||
- job:
|
||||
name: openstack-tox-linters-ansible-2.11
|
||||
parent: openstack-tox-linters
|
||||
nodeset: ubuntu-bionic
|
||||
name: openstack-tox-linters-ansible-2.12
|
||||
parent: openstack-tox-linters-ansible
|
||||
description: |
|
||||
Run openstack collections linter tests using the 2.11 branch of ansible
|
||||
voting: true
|
||||
Run openstack collections linter tests using the 2.12 branch of ansible
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.11
|
||||
override-checkout: stable-2.12
|
||||
vars:
|
||||
tox_envlist: linters-2.12
|
||||
python_version: 3.8
|
||||
bindep_profile: test py38
|
||||
|
||||
- job:
|
||||
name: openstack-tox-linters-ansible-2.9
|
||||
parent: openstack-tox-linters
|
||||
parent: openstack-tox-linters-ansible
|
||||
nodeset: ubuntu-bionic
|
||||
description: |
|
||||
Run openstack collections linter tests using the 2.9 branch of ansible
|
||||
voting: true
|
||||
required-projects:
|
||||
- name: github.com/ansible/ansible
|
||||
override-checkout: stable-2.9
|
||||
@@ -300,20 +506,44 @@
|
||||
parent: bifrost-integration-tinyipa-ubuntu-focal
|
||||
required-projects:
|
||||
- openstack/ansible-collections-openstack
|
||||
|
||||
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
|
||||
name: openstack/bifrost
|
||||
override-checkout: master
|
||||
- job:
|
||||
name: bifrost-keystone-collections-src
|
||||
parent: bifrost-integration-tinyipa-keystone-ubuntu-focal
|
||||
required-projects:
|
||||
- openstack/ansible-collections-openstack
|
||||
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
|
||||
name: openstack/bifrost
|
||||
override-checkout: master
|
||||
|
||||
# TripleO jobs
|
||||
- job:
|
||||
name: tripleo-ci-centos-8-standalone-osa
|
||||
parent: tripleo-ci-centos-8-standalone
|
||||
parent: tripleo-ci-base-standalone-centos-8
|
||||
vars:
|
||||
featureset: '052'
|
||||
consumer_job: false
|
||||
build_container_images: true
|
||||
required-projects:
|
||||
- openstack/ansible-collections-openstack
|
||||
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
|
||||
name: openstack/tripleo-ci
|
||||
override-checkout: master
|
||||
irrelevant-files: &irr_files
|
||||
- .*molecule.*
|
||||
- ^.*\.md$
|
||||
- ^.*\.rst$
|
||||
- ^docs/.*$
|
||||
- ^contrib/.*$
|
||||
- ^changelogs/.*$
|
||||
- ^meta/.*$
|
||||
- ^tests/.*$
|
||||
- ^tools/.*$
|
||||
- ^requirements.*$
|
||||
- ^setup.*$
|
||||
- tox.ini
|
||||
# Run only on files used in TripleO
|
||||
files: &ooo_files
|
||||
- ^plugins/modules/catalog_service.*$
|
||||
@@ -328,46 +558,95 @@
|
||||
- ^plugins/modules/stack.*$
|
||||
- ^plugins/module_utils/openstack.*$
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-9-standalone-osa
|
||||
parent: tripleo-ci-centos-8-standalone-osa
|
||||
nodeset: single-centos-9-node
|
||||
branches: ^(stable/1.0.0|master).*$
|
||||
override-checkout: master
|
||||
vars:
|
||||
containers_base_image: quay.io/centos/centos:stream9
|
||||
consumer_job: false
|
||||
build_container_images: true
|
||||
branch_override: master
|
||||
files: *ooo_files
|
||||
irrelevant-files: *irr_files
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-8-standalone-train-osa
|
||||
parent: tripleo-ci-centos-8-standalone-osa
|
||||
voting: false
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/train
|
||||
vars:
|
||||
branch_override: stable/train
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-8-standalone-wallaby-osa
|
||||
parent: tripleo-ci-centos-8-standalone-osa
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/wallaby
|
||||
vars:
|
||||
branch_override: stable/wallaby
|
||||
|
||||
- job:
|
||||
name: tripleo-ci-centos-9-standalone-wallaby-osa
|
||||
parent: tripleo-ci-centos-9-standalone-osa
|
||||
branches: stable/1.0.0
|
||||
override-checkout: stable/wallaby
|
||||
vars:
|
||||
branch_override: stable/wallaby
|
||||
|
||||
- job:
|
||||
name: ansible-collections-openstack-release
|
||||
parent: base
|
||||
run: ci/publish/publish_collection.yml
|
||||
secrets:
|
||||
- ansible_galaxy_info
|
||||
|
||||
- secret:
|
||||
name: ansible_galaxy_info
|
||||
data:
|
||||
url: https://galaxy.ansible.com
|
||||
token: !encrypted/pkcs1-oaep
|
||||
- lZFzfoCbuwqV1k6qRfl/VS7E+knUW7+zpg7BptrenK4n0g7UY0HtdVkYq0pV0Tj/LbhzG
|
||||
jHD0mehcV1iS6B7ORKg4criJkdDfEx09BD8z8yv0EleiIMmhlrCoMcY593OZMBtVbGi0D
|
||||
CwQtNO98QIsfZogChfLfvRNiBmUV98mEb/p6p3EtGx8J7qcAsqfWxc/CzB8GCleLAHHHT
|
||||
FuikMM03ZnV0ew7E+TPkHbzzPhBZOqS5HYF0HtgttHwIXdfIWp/XdTuEEk7uRRgYZ2Iao
|
||||
ifWRzoKaOQmhM++e1ydCqw9D4y9dZEFNMQLwSqcrvtb8cNwT1kl7SCFqYNE2lbutj4ne6
|
||||
PTBQRsKegMB4Y3ena14fNF6tCynvJLPhF/cjPH2Jhs+B19XQhWkL3TgiOY02W24YHwRcP
|
||||
+LdkM8inAvyVi3DEbEqdjBPO9OFJcBOKPlCdkGvuwdNCuEpEwctWs0gV3voflG2CDKzmJ
|
||||
wu9JJOAWnq/0l1WpuDqWreKeQ/BUGZC2Gb4xRAqofulgvhs4WuYoEccjH4EJFIZ90S1EP
|
||||
R/ZLadqZaEhmjwGM5sMWbBbjT23XsRgg0Tzt9m8DENYMuYDqkMdRbt2jYZa+32p4hyxVe
|
||||
Y6H/pqYq5b9uOzumnShaK4WlmkQyXcNPkoSlMC1h4OGvqX/WUixpI38jyMA5Tc=
|
||||
|
||||
- project:
|
||||
check:
|
||||
jobs:
|
||||
- tox-pep8
|
||||
- openstack-tox-linters-ansible-devel
|
||||
- openstack-tox-linters-ansible-2.11
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack:
|
||||
dependencies: &deps_unit_lint
|
||||
- tox-pep8
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
- openstack-tox-linters-ansible-2.11
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
voting: false
|
||||
|
||||
- ansible-collections-openstack-functional-devstack-releases:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.9:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.11:
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.12:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-ansible-devel:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-wallaby-ansible-2.11:
|
||||
- ansible-collections-openstack-functional-devstack-xena-ansible-2.12:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.11:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.11:
|
||||
- ansible-collections-openstack-functional-devstack-wallaby-ansible-2.12:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-train-ansible-2.11:
|
||||
dependencies: *deps_unit_lint
|
||||
- ansible-collections-openstack-functional-devstack-queens-ansible-2.11:
|
||||
dependencies: *deps_unit_lint
|
||||
voting: false
|
||||
- ansible-collections-openstack-functional-devstack-octavia:
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
@@ -380,52 +659,66 @@
|
||||
dependencies: *deps_unit_lint
|
||||
irrelevant-files: *ignore_files
|
||||
|
||||
- tripleo-ci-centos-8-standalone-osa:
|
||||
- tripleo-ci-centos-8-standalone-wallaby-osa:
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
- tripleo-ci-centos-8-standalone-train-osa:
|
||||
voting: false
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
- tripleo-ci-centos-9-standalone-osa:
|
||||
voting: false
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
- tripleo-ci-centos-9-standalone-wallaby-osa:
|
||||
voting: false
|
||||
dependencies: *deps_unit_lint
|
||||
|
||||
gate:
|
||||
jobs:
|
||||
- tox-pep8
|
||||
- openstack-tox-linters-ansible-2.11
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack
|
||||
# - ansible-collections-openstack-functional-devstack
|
||||
- ansible-collections-openstack-functional-devstack-releases
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-wallaby-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.11
|
||||
# - ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
# - ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-wallaby-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-xena-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-train-ansible-2.11
|
||||
# - ansible-collections-openstack-functional-devstack-queens-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-octavia
|
||||
- tripleo-ci-centos-8-standalone-osa
|
||||
- tripleo-ci-centos-8-standalone-wallaby-osa
|
||||
|
||||
periodic:
|
||||
jobs:
|
||||
- openstack-tox-linters-ansible-devel
|
||||
- openstack-tox-linters-ansible-2.11
|
||||
- openstack-tox-linters-ansible-2.12
|
||||
- openstack-tox-linters-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack
|
||||
- ansible-collections-openstack-functional-devstack-releases
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.9
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-ansible-devel
|
||||
- ansible-collections-openstack-functional-devstack-wallaby-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-xena-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-wallaby-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-train-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-queens-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-queens-ansible-devel
|
||||
- bifrost-collections-src
|
||||
- bifrost-keystone-collections-src
|
||||
- ansible-collections-openstack-functional-devstack-octavia
|
||||
- tripleo-ci-centos-9-standalone-wallaby-osa
|
||||
- tripleo-ci-centos-9-standalone-osa
|
||||
- tripleo-ci-centos-8-standalone-train-osa
|
||||
- tripleo-ci-centos-8-standalone-wallaby-osa
|
||||
|
||||
experimental:
|
||||
jobs:
|
||||
- ansible-collections-openstack-functional-devstack-stein-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-rocky-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-queens-ansible-devel
|
||||
- ansible-collections-openstack-functional-devstack-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.12
|
||||
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.11
|
||||
- ansible-collections-openstack-functional-devstack-queens-ansible-2.11
|
||||
|
||||
tag:
|
||||
jobs:
|
||||
- ansible-collections-openstack-release
|
||||
|
||||
171
CHANGELOG.rst
171
CHANGELOG.rst
@@ -5,6 +5,176 @@ Openstack Cloud Ansilbe modules Release Notes
|
||||
.. contents:: Topics
|
||||
|
||||
|
||||
v1.8.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Subnet pool module and bugfixes
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Add 'all_projects' to server_action module
|
||||
- Add subnet pool module
|
||||
- Bumped minimum required OpenStack SDK release to SDK 0.36.0 (Train)
|
||||
- Changed compute_flavor_info module to use OpenStack SDK's proxy layer
|
||||
- Dropped deprecated return values in floating_ip_info and assert remaining fields
|
||||
- Fix ansible-lint issues for the newest version
|
||||
- Fix assertion after stack deletion
|
||||
- Handle aggregate host list set to None
|
||||
- Reenabled check-import.sh which tests imports to Ansible Galaxy
|
||||
- Remove old, unsupported parameters from documentation in image_info module
|
||||
- Router - Remove unneeded 'filter' parameter
|
||||
- Updated return value docs of compute_service_info module
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- openstack.cloud.subnet_pool - Create or Delete subnet pools from OpenStack.
|
||||
|
||||
v1.7.2
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Fix collection guidelines
|
||||
|
||||
v1.7.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- lb_member - Add monitor_[address,port] parameter
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- openstack_inventory - Fix documentation
|
||||
- quota - Fix description of volumes_types parameter
|
||||
|
||||
v1.7.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
New modules for Ironic and bugfixes
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- openstack_inventory - Adds use_name variable
|
||||
- port - Add dns_[name,domain] to the port module
|
||||
- project - Remove project properties tests and support
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- identity_user_info - Fix identity user lookup with a domain
|
||||
- keystone_domain - Move identity domain to use proxy layer
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- openstack.cloud.baremetal_node_info - Retrieve information about Bare Metal nodes from OpenStack an object.
|
||||
- openstack.cloud.baremetal_port - Create, Update, Remove ironic ports from OpenStack
|
||||
- openstack.cloud.baremetal_port_info - Retrieve information about Bare Metal ports from OpenStack an object.
|
||||
|
||||
v1.6.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
New modules for RBAC and Nova services
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- quota - Adds metadata_items parameter
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- openstack.cloud.compute_service_info - Retrieve information about one or more OpenStack compute services
|
||||
- openstack.cloud.neutron_rbac_policies_info - Fetch Neutron policies.
|
||||
- openstack.cloud.neutron_rbac_policy - Create or delete a Neutron policy to apply a RBAC rule against an object.
|
||||
|
||||
v1.5.3
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Don't require allowed_address_pairs for port
|
||||
- server_volume - check specified server is found
|
||||
|
||||
v1.5.2
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- Add documentation links to README.md
|
||||
- Don't run functional jobs on galaxy.yml change
|
||||
- Move CI to use Ansible 2.12 version as main
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Add client and member listener timeouts for persistence (Ex. SSH)
|
||||
- Added missing warn() used in cloud.openstack.quota
|
||||
- Fix issue with same host and group names
|
||||
- Flavor properties are not deleted on changes and id will stay
|
||||
|
||||
v1.5.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfixes for networking modules
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- Changed minversion in tox to 3.18.0
|
||||
- Update IRC server in README
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Add mandatory requires_ansible version to metadata
|
||||
- Add protocol listener octavia
|
||||
- Add support check mode for all info modules
|
||||
- Allow to attach multiple floating ips to a server
|
||||
- Only add or remove router interfaces when needed
|
||||
- Wait for pool to be active and online
|
||||
|
||||
v1.5.0
|
||||
======
|
||||
|
||||
@@ -74,6 +244,7 @@ Bugfixes
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- openstack.cloud.address_scope - Create or delete address scopes from OpenStack
|
||||
- openstack.cloud.dns_zone_info - Getting information about dns zones
|
||||
- openstack.cloud.floating_ip_info - Get information about floating ips
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -15,7 +15,7 @@ For using the Openstack Cloud collection firstly you need to install `ansible` a
|
||||
For example with pip:
|
||||
|
||||
```bash
|
||||
pip install ansible openstacksdk
|
||||
pip install "ansible>=2.9" "openstacksdk>=0.36"
|
||||
```
|
||||
|
||||
OpenStackSDK has to be available to Ansible and to the Python interpreter on the host, where Ansible executes the module (target host).
|
||||
@@ -83,6 +83,14 @@ Or you can add the full namespace and collection name in the `collections` eleme
|
||||
device: /dev/vdb
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
See the collection docs at Ansible site:
|
||||
|
||||
* [openstack.cloud collection docs (version released in Ansible package)](https://docs.ansible.com/ansible/latest/collections/openstack/cloud/index.html)
|
||||
|
||||
* [openstack.cloud collection docs (devel version)](https://docs.ansible.com/ansible/devel/collections/openstack/cloud/index.html)
|
||||
|
||||
## Contributing
|
||||
|
||||
For information on contributing, please see [CONTRIBUTING](https://opendev.org/openstack/ansible-collections-openstack/src/branch/master/CONTRIBUTING.rst)
|
||||
@@ -114,7 +122,7 @@ TBD
|
||||
## Communication
|
||||
|
||||
We have a dedicated Interest Group for Openstack Ansible modules.
|
||||
You can find other people interested in this in `#openstack-ansible-sig` on Freenode IRC.
|
||||
You can find other people interested in this in `#openstack-ansible-sig` on [OFTC IRC](https://www.oftc.net/).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -222,6 +222,9 @@ releases:
|
||||
- image - Add support to setting image tags
|
||||
release_summary: New modules for DNS and FIPs and bugfixes.
|
||||
modules:
|
||||
- description: Create or delete address scopes from OpenStack
|
||||
name: address_scope
|
||||
namespace: ''
|
||||
- description: Getting information about dns zones
|
||||
name: dns_zone_info
|
||||
namespace: ''
|
||||
@@ -229,3 +232,112 @@ releases:
|
||||
name: floating_ip_info
|
||||
namespace: ''
|
||||
release_date: '2021-06-23'
|
||||
1.5.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Add mandatory requires_ansible version to metadata
|
||||
- Add protocol listener octavia
|
||||
- Add support check mode for all info modules
|
||||
- Allow to attach multiple floating ips to a server
|
||||
- Only add or remove router interfaces when needed
|
||||
- Wait for pool to be active and online
|
||||
minor_changes:
|
||||
- Changed minversion in tox to 3.18.0
|
||||
- Update IRC server in README
|
||||
release_summary: Bugfixes for networking modules
|
||||
release_date: '2021-09-02'
|
||||
1.5.2:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Add client and member listener timeouts for persistence (Ex. SSH)
|
||||
- Added missing warn() used in cloud.openstack.quota
|
||||
- Fix issue with same host and group names
|
||||
- Flavor properties are not deleted on changes and id will stay
|
||||
minor_changes:
|
||||
- Add documentation links to README.md
|
||||
- Don't run functional jobs on galaxy.yml change
|
||||
- Move CI to use Ansible 2.12 version as main
|
||||
release_summary: Bugfixes
|
||||
release_date: '2021-11-09'
|
||||
1.5.3:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Don't require allowed_address_pairs for port
|
||||
- server_volume - check specified server is found
|
||||
release_summary: Bugfixes
|
||||
release_date: '2021-11-11'
|
||||
1.6.0:
|
||||
changes:
|
||||
minor_changes:
|
||||
- quota - Adds metadata_items parameter
|
||||
release_summary: New modules for RBAC and Nova services
|
||||
modules:
|
||||
- description: Retrieve information about one or more OpenStack compute services
|
||||
name: compute_service_info
|
||||
namespace: ''
|
||||
- description: Fetch Neutron policies.
|
||||
name: neutron_rbac_policies_info
|
||||
namespace: ''
|
||||
- description: Create or delete a Neutron policy to apply a RBAC rule against
|
||||
an object.
|
||||
name: neutron_rbac_policy
|
||||
namespace: ''
|
||||
release_date: '2022-01-13'
|
||||
1.7.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- identity_user_info - Fix identity user lookup with a domain
|
||||
- keystone_domain - Move identity domain to use proxy layer
|
||||
minor_changes:
|
||||
- openstack_inventory - Adds use_name variable
|
||||
- port - Add dns_[name,domain] to the port module
|
||||
- project - Remove project properties tests and support
|
||||
release_summary: New modules for Ironic and bugfixes
|
||||
modules:
|
||||
- description: Retrieve information about Bare Metal nodes from OpenStack an object.
|
||||
name: baremetal_node_info
|
||||
namespace: ''
|
||||
- description: Create, Update, Remove ironic ports from OpenStack
|
||||
name: baremetal_port
|
||||
namespace: ''
|
||||
- description: Retrieve information about Bare Metal ports from OpenStack an object.
|
||||
name: baremetal_port_info
|
||||
namespace: ''
|
||||
release_date: '2022-02-15'
|
||||
1.7.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- openstack_inventory - Fix documentation
|
||||
- quota - Fix description of volumes_types parameter
|
||||
minor_changes:
|
||||
- lb_member - Add monitor_[address,port] parameter
|
||||
release_summary: Bugfixes
|
||||
release_date: '2022-03-08'
|
||||
1.7.2:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Fix collection guidelines
|
||||
release_summary: Bugfixes
|
||||
release_date: '2022-03-10'
|
||||
1.8.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Add 'all_projects' to server_action module
|
||||
- Add subnet pool module
|
||||
- Bumped minimum required OpenStack SDK release to SDK 0.36.0 (Train)
|
||||
- Changed compute_flavor_info module to use OpenStack SDK's proxy layer
|
||||
- Dropped deprecated return values in floating_ip_info and assert remaining
|
||||
fields
|
||||
- Fix ansible-lint issues for the newest version
|
||||
- Fix assertion after stack deletion
|
||||
- Handle aggregate host list set to None
|
||||
- Reenabled check-import.sh which tests imports to Ansible Galaxy
|
||||
- Remove old, unsupported parameters from documentation in image_info module
|
||||
- Router - Remove unneeded 'filter' parameter
|
||||
- Updated return value docs of compute_service_info module
|
||||
release_summary: Subnet pool module and bugfixes
|
||||
modules:
|
||||
- description: Create or Delete subnet pools from OpenStack.
|
||||
name: subnet_pool
|
||||
namespace: ''
|
||||
release_date: '2022-04-08'
|
||||
|
||||
8
ci/playbooks/postlog.yaml
Normal file
8
ci/playbooks/postlog.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
- hosts: all
|
||||
tasks:
|
||||
- zuul_return:
|
||||
data:
|
||||
zuul:
|
||||
artifacts:
|
||||
- name: Test log
|
||||
url: controller/logs/test_output_log.txt
|
||||
75
ci/publish/publish_collection.yml
Normal file
75
ci/publish/publish_collection.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
collection_path: "{{ ansible_user_dir }}/{{ zuul.projects['opendev.org/openstack/ansible-collections-openstack'].src_dir }}"
|
||||
build_collection_path: /tmp/collection_built/
|
||||
ansible_galaxy_path: "~/.local/bin/ansible-galaxy"
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Include role for pip
|
||||
include_role:
|
||||
name: ensure-pip
|
||||
|
||||
- name: Install ansible
|
||||
pip:
|
||||
name: ansible-core<2.12
|
||||
|
||||
- name: Discover tag version
|
||||
set_fact:
|
||||
version_tag: "{{ zuul.tag|default('no_version', true) }}"
|
||||
|
||||
- name: Fail if no tag version found
|
||||
fail:
|
||||
msg: "No tag was found in Zuul vars!"
|
||||
when: version_tag == 'no_version'
|
||||
|
||||
- name: Create a directory for collection
|
||||
file:
|
||||
state: "{{ item }}"
|
||||
path: "{{ build_collection_path }}"
|
||||
loop:
|
||||
- absent
|
||||
- directory
|
||||
|
||||
- name: Set galaxy.yml for right version from tag
|
||||
lineinfile:
|
||||
path: '{{ collection_path }}/galaxy.yml'
|
||||
regexp: '^version:.*'
|
||||
line: 'version: {{ version_tag }}'
|
||||
|
||||
- name: Build collection
|
||||
command: "{{ ansible_galaxy_path }} collection build --output-path {{ build_collection_path }} --force"
|
||||
args:
|
||||
chdir: "{{ collection_path }}"
|
||||
|
||||
- name: Publish content to Ansible Galaxy
|
||||
block:
|
||||
- name: Create ansible.cfg configuration file tempfile
|
||||
tempfile:
|
||||
state: file
|
||||
suffix: .cfg
|
||||
register: _ansiblecfg_tmp
|
||||
|
||||
- name: Create ansible.cfg configuration file
|
||||
copy:
|
||||
dest: "{{ _ansiblecfg_tmp.path }}"
|
||||
mode: 0600
|
||||
content: |
|
||||
[galaxy]
|
||||
server_list = release_galaxy
|
||||
|
||||
[galaxy_server.release_galaxy]
|
||||
url = {{ ansible_galaxy_info.url }}
|
||||
token = {{ ansible_galaxy_info.token }}
|
||||
|
||||
- name: Publish collection to Ansible Galaxy / Automation Hub
|
||||
environment:
|
||||
ANSIBLE_CONFIG: "{{ _ansiblecfg_tmp.path }}"
|
||||
shell: >-
|
||||
{{ ansible_galaxy_path }} collection publish -vvv
|
||||
{{ build_collection_path }}/openstack-cloud-{{ version_tag }}.tar.gz
|
||||
|
||||
always:
|
||||
- name: Shred ansible-galaxy credentials
|
||||
command: "shred {{ _ansiblecfg_tmp.path }}"
|
||||
33
ci/roles/compute_flavor_info/tasks/main.yml
Normal file
33
ci/roles/compute_flavor_info/tasks/main.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
- name: List flavors
|
||||
openstack.cloud.compute_flavor_info:
|
||||
cloud: "{{ cloud }}"
|
||||
|
||||
- name: List flavors with filter
|
||||
openstack.cloud.compute_flavor_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "m1.tiny"
|
||||
register: flavor_name
|
||||
|
||||
- name: Check output of list flavors with filter
|
||||
assert:
|
||||
that:
|
||||
- flavor_name.openstack_flavors | length == 1
|
||||
|
||||
- name: Assert fields on SDK 0.*
|
||||
when: sdk_version is version(1.0, '<')
|
||||
assert:
|
||||
that:
|
||||
- '["name", "description", "disk", "is_public", "ram",
|
||||
"vcpus", "swap", "ephemeral", "is_disabled", "rxtx_factor", "id",
|
||||
"extra_specs"] | difference(flavor_name.openstack_flavors.0.keys())
|
||||
| length == 0'
|
||||
|
||||
- name: Assert fields on SDK 1.*
|
||||
when: sdk_version is version(1.0, '>=')
|
||||
assert:
|
||||
that:
|
||||
- '["name", "original_name", "description", "disk", "is_public", "ram",
|
||||
"vcpus", "swap", "ephemeral", "is_disabled", "rxtx_factor", "id",
|
||||
"extra_specs"] | difference(flavor_name.openstack_flavors.0.keys())
|
||||
| length == 0'
|
||||
466
ci/roles/floating_ip/tasks/main.yml
Normal file
466
ci/roles/floating_ip/tasks/main.yml
Normal file
@@ -0,0 +1,466 @@
|
||||
---
|
||||
# Prepare environment
|
||||
- name: Gather information about public network
|
||||
openstack.cloud.networks_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: public
|
||||
register: public_network
|
||||
|
||||
- name: Assert that public network exists
|
||||
assert:
|
||||
that: public_network.openstack_networks|length == 1
|
||||
|
||||
- name: Create external network
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_external
|
||||
external: true
|
||||
|
||||
- name: Create external subnet
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
network_name: ansible_external
|
||||
name: ansible_external_subnet
|
||||
cidr: 10.6.6.0/24
|
||||
|
||||
- name: Create external port 1
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_external_port1
|
||||
network: ansible_external
|
||||
fixed_ips:
|
||||
- ip_address: 10.6.6.50
|
||||
|
||||
- name: Create external port 2
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_external_port2
|
||||
network: ansible_external
|
||||
fixed_ips:
|
||||
- ip_address: 10.6.6.51
|
||||
|
||||
- name: Create internal network
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_internal
|
||||
external: false
|
||||
|
||||
- name: Create internal subnet
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
network_name: ansible_internal
|
||||
name: ansible_internal_subnet
|
||||
cidr: 10.7.7.0/24
|
||||
|
||||
- name: Create internal port 1
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_internal_port1
|
||||
network: ansible_internal
|
||||
fixed_ips:
|
||||
- ip_address: 10.7.7.100
|
||||
|
||||
- name: Create internal port 2
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_internal_port2
|
||||
network: ansible_internal
|
||||
fixed_ips:
|
||||
- ip_address: 10.7.7.101
|
||||
|
||||
- name: Create internal port 3
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_internal_port3
|
||||
network: ansible_internal
|
||||
fixed_ips:
|
||||
- ip_address: 10.7.7.102
|
||||
|
||||
- name: Create router 1
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_router1
|
||||
network: ansible_external
|
||||
external_fixed_ips:
|
||||
- subnet: ansible_external_subnet
|
||||
ip: 10.6.6.10
|
||||
interfaces:
|
||||
- net: ansible_internal
|
||||
subnet: ansible_internal_subnet
|
||||
portip: 10.7.7.1
|
||||
|
||||
# Router 2 is required for the simplest, first test that assigns a new floating IP to server
|
||||
# from first available external network or nova pool which is DevStack's public network
|
||||
- name: Create router 2
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_router2
|
||||
network: public
|
||||
interfaces:
|
||||
- net: ansible_internal
|
||||
subnet: ansible_internal_subnet
|
||||
portip: 10.7.7.10
|
||||
|
||||
- name: Get all floating ips
|
||||
openstack.cloud.floating_ip_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: fips
|
||||
|
||||
- name: Check if public network has any floating ips
|
||||
set_fact:
|
||||
public_network_had_fips: "{{ fips.floating_ips|
|
||||
selectattr('floating_network_id', '==', public_network.openstack_networks.0.id)|
|
||||
list|length > 0 }}"
|
||||
|
||||
# TODO: Replace with appropriate Ansible module once available
|
||||
- name: Create a floating ip on public network (required for simplest, first floating ip test)
|
||||
command: openstack --os-cloud={{ cloud }} floating ip create public
|
||||
when: not public_network_had_fips
|
||||
|
||||
# TODO: Replace with appropriate Ansible module once available
|
||||
- name: Create floating ip 1 on external network
|
||||
command: >
|
||||
openstack --os-cloud={{ cloud }} floating ip create
|
||||
--subnet ansible_external_subnet
|
||||
--floating-ip-address 10.6.6.150
|
||||
ansible_external
|
||||
when: fips.floating_ips|length == 0 or
|
||||
"10.6.6.150" not in fips.floating_ips|map(attribute="floating_ip_address")|list
|
||||
|
||||
- name: Create server with one nic
|
||||
openstack.cloud.server:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_server1
|
||||
image: "{{ image }}"
|
||||
flavor: m1.tiny
|
||||
nics:
|
||||
# one nic only else simple, first floating ip test does not work
|
||||
- port-name: ansible_internal_port1
|
||||
auto_ip: false
|
||||
wait: true
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server1
|
||||
register: info
|
||||
|
||||
- name: Assert one internal port and no floating ips on server 1
|
||||
# If this assertion fails because server has an public ipv4 address (public_v4) then make sure
|
||||
# that no floating ip on public network is associated with "10.7.7.100" before running this role
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers|length == 1
|
||||
- info.openstack_servers.0.public_v4|length == 0
|
||||
- info.openstack_servers.0.public_v6|length == 0
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 1
|
||||
- info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list == ["10.7.7.100"]
|
||||
|
||||
- name: Create server with two nics
|
||||
openstack.cloud.server:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_server2
|
||||
image: "{{ image }}"
|
||||
flavor: m1.tiny
|
||||
nics:
|
||||
- port-name: ansible_internal_port2
|
||||
- port-name: ansible_internal_port3
|
||||
auto_ip: false
|
||||
wait: true
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server2
|
||||
register: info
|
||||
|
||||
- name: Assert two internal ports and no floating ips on server 2
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers|length == 1
|
||||
- info.openstack_servers.0.public_v4|length == 0
|
||||
- info.openstack_servers.0.public_v6|length == 0
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 2
|
||||
- info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list ==
|
||||
["10.7.7.101", "10.7.7.102"]
|
||||
|
||||
# Tests
|
||||
- name: Assign new floating IP to server from first available external network or nova pool
|
||||
openstack.cloud.floating_ip:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
server: ansible_server1
|
||||
wait: true
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server1
|
||||
register: info
|
||||
|
||||
- name: Assert one internal port and one floating ip on server 1
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 2
|
||||
- info.openstack_servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list ==
|
||||
["fixed", "floating"]
|
||||
|
||||
- name: Detach floating IP from server
|
||||
openstack.cloud.floating_ip:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
server: ansible_server1
|
||||
network: public
|
||||
floating_ip_address: "{{ (info.openstack_servers.0.addresses.ansible_internal|
|
||||
selectattr('OS-EXT-IPS:type', '==', 'floating')|map(attribute='addr')|list)[0] }}"
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server1
|
||||
register: info
|
||||
# When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info
|
||||
# does not list it any more in info.openstack_servers.0.addresses.ansible_internal, so retry if necessary.
|
||||
retries: 10
|
||||
delay: 3
|
||||
until: info.openstack_servers.0.addresses.ansible_internal|length == 1
|
||||
|
||||
- name: Assert one internal port on server 1
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 1
|
||||
- info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.100"]
|
||||
|
||||
- name: Assign floating IP to server
|
||||
openstack.cloud.floating_ip:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
reuse: yes
|
||||
server: ansible_server2
|
||||
network: public
|
||||
fixed_address: 10.7.7.101
|
||||
wait: true
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server2
|
||||
register: info
|
||||
|
||||
- name: Assert two internal ports and one floating ip on server 2
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 3
|
||||
- info.openstack_servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list ==
|
||||
["fixed", "fixed", "floating"]
|
||||
|
||||
- name: Assign a second, specific floating IP to server
|
||||
openstack.cloud.floating_ip:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
reuse: yes
|
||||
server: ansible_server2
|
||||
network: ansible_external
|
||||
fixed_address: 10.7.7.102
|
||||
floating_ip_address: "10.6.6.150"
|
||||
|
||||
# We cannot wait for second floating ip to be attached because OpenStackSDK checks only for first floating ip
|
||||
# Ref.: https://github.com/openstack/openstacksdk/blob/e0372b72af8c5f471fc17e53434d7a814ca958bd/openstack/cloud/_floating_ip.py#L733
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server2
|
||||
register: info
|
||||
# retry because we cannot wait for second floating ip
|
||||
retries: 10
|
||||
delay: 3
|
||||
until: info.openstack_servers.0.addresses.ansible_internal|length == 4
|
||||
|
||||
- name: Assert two internal ports and two floating ips on server 2
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 4
|
||||
- ("10.6.6.150" in info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list)
|
||||
|
||||
- name: Detach second floating IP from server
|
||||
openstack.cloud.floating_ip:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
server: ansible_server2
|
||||
network: ansible_external
|
||||
floating_ip_address: "10.6.6.150"
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server2
|
||||
register: info
|
||||
# When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info
|
||||
# does not list it any more in info.openstack_servers.0.addresses.ansible_internal, so retry if necessary.
|
||||
retries: 10
|
||||
delay: 3
|
||||
until: info.openstack_servers.0.addresses.ansible_internal|length == 3
|
||||
|
||||
- name: Assert two internal ports and one floating ip on server 2
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 3
|
||||
|
||||
- name: Detach remaining floating IP from server
|
||||
openstack.cloud.floating_ip:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
server: ansible_server2
|
||||
network: public
|
||||
floating_ip_address: "{{ (info.openstack_servers.0.addresses.ansible_internal|
|
||||
selectattr('OS-EXT-IPS:type', '==', 'floating')|map(attribute='addr')|list)[0] }}"
|
||||
|
||||
- name: Get info about server
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud }}"
|
||||
server: ansible_server2
|
||||
register: info
|
||||
# When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info
|
||||
# does not list it any more in info.openstack_servers.0.addresses.ansible_internal, so retry if necessary.
|
||||
retries: 10
|
||||
delay: 3
|
||||
until: info.openstack_servers.0.addresses.ansible_internal|length == 2
|
||||
|
||||
- name: Assert two internal ports on server 2
|
||||
assert:
|
||||
that:
|
||||
- info.openstack_servers.0.addresses.ansible_internal|length == 2
|
||||
- info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.101", "10.7.7.102"]
|
||||
|
||||
# Clean environment
|
||||
- name: Delete server with two nics
|
||||
openstack.cloud.server:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_server2
|
||||
wait: true
|
||||
|
||||
- name: Delete server with one nic
|
||||
openstack.cloud.server:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_server1
|
||||
wait: true
|
||||
|
||||
- name: Get all floating ips
|
||||
openstack.cloud.floating_ip_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: fips
|
||||
|
||||
# TODO: Replace with appropriate Ansible module once available
|
||||
- name: Delete floating ip on public network if we created it
|
||||
when: not public_network_had_fips
|
||||
command: >
|
||||
openstack --os-cloud={{ cloud }} floating ip delete
|
||||
{{ fips.floating_ips|selectattr('floating_network_id', '==', public_network.openstack_networks.0.id)|
|
||||
map(attribute="floating_ip_address")|list|join(' ') }}
|
||||
|
||||
# TODO: Replace with appropriate Ansible module once available
|
||||
- name: Delete floating ip 1
|
||||
command: openstack --os-cloud={{ cloud }} floating ip delete 10.6.6.150
|
||||
when: fips.floating_ips|length > 0 and "10.6.6.150" in fips.floating_ips|map(attribute="floating_ip_address")|list
|
||||
|
||||
- name: Get remaining floating ips on external network
|
||||
openstack.cloud.floating_ip_info:
|
||||
cloud: "{{ cloud }}"
|
||||
floating_network: ansible_external
|
||||
register: fips
|
||||
|
||||
# TODO: Replace with appropriate Ansible module once available
|
||||
# The first, simple floating ip test might have allocated a floating ip on the external network.
|
||||
# This floating ip must be removed before external network can be deleted.
|
||||
- name: Delete remaining floating ips on external network
|
||||
when: fips.floating_ips|length > 0
|
||||
command: >
|
||||
openstack --os-cloud={{ cloud }} floating ip delete
|
||||
{{ fips.floating_ips|map(attribute="floating_ip_address")|list|join(' ') }}
|
||||
|
||||
# Remove routers after floating ips have been detached and disassociated else removal fails with
|
||||
# Error detaching interface from router ***: Client Error for url: ***,
|
||||
# Router interface for subnet *** on router *** cannot be deleted,
|
||||
# as it is required by one or more floating IPs.
|
||||
|
||||
- name: Delete router 2
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_router2
|
||||
|
||||
- name: Delete router 1
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_router1
|
||||
|
||||
- name: Delete internal port 3
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_internal_port3
|
||||
|
||||
- name: Delete internal port 2
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_internal_port2
|
||||
|
||||
- name: Delete internal port 1
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_internal_port1
|
||||
|
||||
- name: Delete internal subnet
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_internal_subnet
|
||||
|
||||
- name: Delete internal network
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_internal
|
||||
|
||||
- name: Delete external port 2
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_external_port2
|
||||
|
||||
- name: Delete external port 1
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_external_port1
|
||||
|
||||
- name: Delete external subnet
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_external_subnet
|
||||
|
||||
- name: Delete external network
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_external
|
||||
@@ -9,3 +9,13 @@
|
||||
that:
|
||||
- fips is success
|
||||
- fips is not changed
|
||||
|
||||
- name: assert fields
|
||||
when: fips.floating_ips|length > 0
|
||||
assert:
|
||||
that:
|
||||
# allow new fields to be introduced but prevent fields from being removed
|
||||
- '["created_at", "description", "dns_domain", "dns_name", "fixed_ip_address", "floating_ip_address",
|
||||
"floating_network_id", "id", "name", "port_details", "port_id", "project_id", "qos_policy_id",
|
||||
"revision_number", "router_id", "status", "subnet_id", "tags", "updated_at"]|
|
||||
difference(fips.floating_ips.0.keys())|length == 0'
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
---
|
||||
- name: Create keystone domain
|
||||
openstack.cloud.identity_domain:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ domain_name }}"
|
||||
description: "test description"
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ domain_name }}"
|
||||
description: "test description"
|
||||
register: os_domain
|
||||
|
||||
- name: Test output
|
||||
assert:
|
||||
that:
|
||||
- "'domain' in os_domain"
|
||||
- os_domain.domain.name == "{{ domain_name }}"
|
||||
- >-
|
||||
('enabled' in os_domain.domain.keys() and os_domain.domain['enabled']|bool) or
|
||||
('is_enabled' in os_domain.domain and os_domain.domain['is_enabled']|bool)
|
||||
- os_domain.domain.description == "test description"
|
||||
|
||||
- name: Update keystone domain
|
||||
openstack.cloud.identity_domain:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ domain_name }}"
|
||||
description: "updated description"
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ domain_name }}"
|
||||
description: "updated description"
|
||||
register: os_domain_updated
|
||||
|
||||
- name: Test output
|
||||
assert:
|
||||
that:
|
||||
- os_domain_updated.domain.description == "updated description"
|
||||
|
||||
- name: Delete keystone domain
|
||||
openstack.cloud.identity_domain:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ domain_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ domain_name }}"
|
||||
|
||||
85
ci/roles/neutron_rbac/tasks/main.yml
Normal file
85
ci/roles/neutron_rbac/tasks/main.yml
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
# General run of tests
|
||||
# - Prepare projects/network objects
|
||||
# - Create rbac object
|
||||
# - Get rbac object info
|
||||
# - Verify RBAC object match
|
||||
# - Delete rbac object
|
||||
# - Get rbac object info
|
||||
# - Verify RBAC object deleted
|
||||
|
||||
- name: Create source project
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: source_project
|
||||
description: Source project for network RBAC test
|
||||
domain_id: default
|
||||
enabled: True
|
||||
register: source_project
|
||||
|
||||
- name: Create network - generic
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ network_name }}"
|
||||
state: present
|
||||
project: "{{ source_project.project.id }}"
|
||||
shared: false
|
||||
external: "{{ network_external }}"
|
||||
register: network
|
||||
|
||||
- name: Create target project
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: Target project for network RBAC test
|
||||
domain_id: default
|
||||
enabled: True
|
||||
register: target_project
|
||||
|
||||
- name: Create a new network RBAC policy
|
||||
openstack.cloud.neutron_rbac_policy:
|
||||
cloud: "{{ cloud }}"
|
||||
object_id: "{{ network.network.id }}"
|
||||
object_type: 'network'
|
||||
action: 'access_as_shared'
|
||||
target_project_id: "{{ target_project.project.id }}"
|
||||
project_id: "{{ source_project.project.id }}"
|
||||
register: rbac_policy
|
||||
|
||||
- name: Get all rbac policies for {{ source_project.project.name }} - after creation
|
||||
openstack.cloud.neutron_rbac_policies_info:
|
||||
cloud: "{{ cloud }}"
|
||||
project_id: "{{ source_project.project.id }}"
|
||||
register: rbac_policies
|
||||
|
||||
- name: Capture all existing policy IDs
|
||||
set_fact:
|
||||
rbac_policy_ids: "{{ rbac_policies.policies | map(attribute='id') | list }}"
|
||||
|
||||
- name: Verify policy exists - after creation
|
||||
assert:
|
||||
that:
|
||||
- rbac_policy.policy.id in rbac_policy_ids
|
||||
|
||||
- name: Delete RBAC policy
|
||||
openstack.cloud.neutron_rbac_policy:
|
||||
cloud: "{{ cloud }}"
|
||||
policy_id: "{{ rbac_policy.policy.id }}"
|
||||
state: absent
|
||||
|
||||
- name: Get all rbac policies for {{ source_project.project.name }} - after deletion
|
||||
openstack.cloud.neutron_rbac_policies_info:
|
||||
cloud: "{{ cloud }}"
|
||||
project_id: "{{ source_project.project.id }}"
|
||||
register: rbac_policies_remaining
|
||||
|
||||
- name: Capture all remaining policy IDs
|
||||
set_fact:
|
||||
remaining_rbac_policy_ids: "{{ rbac_policies_remaining.policies | map(attribute='id') | list }}"
|
||||
|
||||
- name: Verify policy does not exist - after deletion
|
||||
assert:
|
||||
that:
|
||||
- not rbac_policy.policy.id in remaining_rbac_policy_ids
|
||||
22
ci/roles/nova_services/tasks/main.yml
Normal file
22
ci/roles/nova_services/tasks/main.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
|
||||
- name: Get nova compute services info
|
||||
openstack.cloud.compute_service_info:
|
||||
cloud: "{{ cloud }}"
|
||||
binary: "nova-compute"
|
||||
register: result
|
||||
failed_when: "result.openstack_compute_services | length <= 0"
|
||||
|
||||
- name: Assert fields
|
||||
assert:
|
||||
that:
|
||||
- '["availability_zone", "binary", "disabled_reason", "is_forced_down",
|
||||
"host", "name", "state", "status", "updated_at", "id"] |
|
||||
difference(result.openstack_compute_services.0.keys()) | length == 0'
|
||||
|
||||
- name: Get nova conductor services info
|
||||
openstack.cloud.compute_service_info:
|
||||
cloud: "{{ cloud }}"
|
||||
binary: "nova-conductor"
|
||||
register: result
|
||||
failed_when: "result.openstack_compute_services | length <= 0"
|
||||
@@ -41,4 +41,4 @@
|
||||
- assert:
|
||||
that:
|
||||
- stacks is defined
|
||||
- stacks['stacks']|length == 0
|
||||
- (stacks['stacks']|length == 0) or (stacks['stacks'][0]['status'] == 'DELETE_COMPLETE')
|
||||
|
||||
@@ -60,6 +60,26 @@
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Create port (with dns_name, dns_domain)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
dns_name: "dns-port-name"
|
||||
dns_domain: "example.com."
|
||||
register: port
|
||||
|
||||
- debug: var=port
|
||||
|
||||
- name: Delete port (with dns name,domain)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Create port (with allowed_address_pairs and extra_dhcp_opts)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
@@ -101,7 +121,7 @@
|
||||
|
||||
- name: Assert binding:profile exists in created port
|
||||
assert:
|
||||
that: "port.port['binding:profile']"
|
||||
that: "port.port['binding_profile']"
|
||||
|
||||
- debug: var=port
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
dummy_value: 'test-value'
|
||||
dummy_value_updated: 'test-value-updated'
|
||||
@@ -1,142 +0,0 @@
|
||||
---
|
||||
- name: 'Create project with properties - CHECK_MODE'
|
||||
check_mode: yes
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: dummy description
|
||||
domain_id: default
|
||||
enabled: True
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value }}'
|
||||
register: create_project_cm
|
||||
- assert:
|
||||
that:
|
||||
- create_project_cm is successful
|
||||
- create_project_cm is changed
|
||||
|
||||
- name: 'Create project with properties'
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: dummy description
|
||||
domain_id: default
|
||||
enabled: True
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value }}'
|
||||
register: create_project
|
||||
- assert:
|
||||
that:
|
||||
- create_project is successful
|
||||
- create_project is changed
|
||||
- '"project" in create_project'
|
||||
- '"dummy_key" in create_project["project"]'
|
||||
- create_project["project"].dummy_key == dummy_value
|
||||
|
||||
- name: 'Create project with properties (retry - no change) - CHECK_MODE'
|
||||
check_mode: yes
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: dummy description
|
||||
domain_id: default
|
||||
enabled: True
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value }}'
|
||||
register: create_project_retry_cm
|
||||
- assert:
|
||||
that:
|
||||
- create_project_retry_cm is successful
|
||||
- create_project_retry_cm is not changed
|
||||
|
||||
- name: 'Create project with properties (retry - no change)'
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: dummy description
|
||||
domain_id: default
|
||||
enabled: True
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value }}'
|
||||
register: create_project_retry
|
||||
- assert:
|
||||
that:
|
||||
- create_project_retry is successful
|
||||
- create_project_retry is not changed
|
||||
- '"project" in create_project_retry'
|
||||
- '"dummy_key" in create_project_retry["project"]'
|
||||
- create_project_retry["project"].dummy_key == dummy_value
|
||||
|
||||
- name: 'Update project with properties - CHECK_MODE'
|
||||
check_mode: yes
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: new description
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value_updated }}'
|
||||
register: updated_project_cm
|
||||
- assert:
|
||||
that:
|
||||
- updated_project_cm is successful
|
||||
- updated_project_cm is changed
|
||||
|
||||
- name: 'Update project with properties'
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: new description
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value_updated }}'
|
||||
register: updated_project
|
||||
- assert:
|
||||
that:
|
||||
- updated_project is successful
|
||||
- updated_project is changed
|
||||
- '"project" in updated_project'
|
||||
- '"dummy_key" in updated_project["project"]'
|
||||
- updated_project["project"].dummy_key == dummy_value_updated
|
||||
|
||||
- name: 'Update project with properties (retry - no change) - CHECK_MODE'
|
||||
check_mode: yes
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: new description
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value_updated }}'
|
||||
register: updated_project_retry_cm
|
||||
- assert:
|
||||
that:
|
||||
- updated_project_retry_cm is successful
|
||||
- updated_project_retry_cm is not changed
|
||||
|
||||
- name: 'Update project with properties (retry - no change)'
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: ansible_project
|
||||
description: new description
|
||||
properties:
|
||||
dummy_key: '{{ dummy_value_updated }}'
|
||||
register: updated_project_retry
|
||||
- assert:
|
||||
that:
|
||||
- updated_project_retry is successful
|
||||
- updated_project_retry is not changed
|
||||
- '"project" in updated_project_retry'
|
||||
- '"dummy_key" in updated_project_retry["project"]'
|
||||
- updated_project_retry["project"].dummy_key == dummy_value_updated
|
||||
|
||||
- name: Delete project with properties
|
||||
openstack.cloud.project:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: ansible_project
|
||||
@@ -15,6 +15,30 @@
|
||||
name: shade_subnet1
|
||||
cidr: 10.7.7.0/24
|
||||
|
||||
- name: Create subnet2
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
network_name: "{{ network_name }}"
|
||||
name: shade_subnet2
|
||||
cidr: 10.8.8.0/24
|
||||
|
||||
- name: Create subnet3
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
network_name: "{{ network_name }}"
|
||||
name: shade_subnet3
|
||||
cidr: 10.9.9.0/24
|
||||
|
||||
- name: Create subnet4
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
network_name: "{{ network_name }}"
|
||||
name: shade_subnet4
|
||||
cidr: 10.10.10.0/24
|
||||
|
||||
- name: Create router
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
@@ -29,6 +53,19 @@
|
||||
interfaces:
|
||||
- shade_subnet1
|
||||
|
||||
- name: Update router (add interface) again
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- shade_subnet1
|
||||
register: result
|
||||
|
||||
- name: Assert idempotent module
|
||||
assert:
|
||||
that: result is not changed
|
||||
|
||||
- name: Gather routers info
|
||||
openstack.cloud.routers_info:
|
||||
cloud: "{{ cloud }}"
|
||||
@@ -43,6 +80,93 @@
|
||||
- "result.openstack_routers.0.name == router_name"
|
||||
- (result.openstack_routers.0.interfaces_info|length) == 1
|
||||
|
||||
- name: Update router (change interfaces)
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- net: '{{ network_name }}'
|
||||
subnet: shade_subnet2
|
||||
portip: 10.8.8.1
|
||||
- net: '{{ network_name }}'
|
||||
subnet: shade_subnet3
|
||||
- shade_subnet4
|
||||
|
||||
- name: Update router (change interfaces) again
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- net: '{{ network_name }}'
|
||||
subnet: shade_subnet2
|
||||
portip: 10.8.8.1
|
||||
- net: '{{ network_name }}'
|
||||
subnet: shade_subnet3
|
||||
- shade_subnet4
|
||||
register: result
|
||||
|
||||
- name: Assert idempotent module
|
||||
assert:
|
||||
that: result is not changed
|
||||
|
||||
- name: Gather routers info
|
||||
openstack.cloud.routers_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ router_name }}"
|
||||
filters:
|
||||
admin_state_up: true
|
||||
register: result
|
||||
|
||||
- name: Verify routers info
|
||||
assert:
|
||||
that:
|
||||
- "result.openstack_routers.0.name == router_name"
|
||||
- (result.openstack_routers.0.interfaces_info|length) == 3
|
||||
- result.openstack_routers.0.interfaces_info|map(attribute='ip_address')|sort|list ==
|
||||
['10.10.10.1', '10.8.8.1', '10.9.9.1']
|
||||
|
||||
- name: Update router (remove interface)
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- net: '{{ network_name }}'
|
||||
subnet: shade_subnet1
|
||||
portip: 10.7.7.1
|
||||
|
||||
- name: Update router (remove interface) again
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- net: '{{ network_name }}'
|
||||
subnet: shade_subnet1
|
||||
portip: 10.7.7.1
|
||||
register: result
|
||||
|
||||
- name: Assert idempotent module
|
||||
assert:
|
||||
that: result is not changed
|
||||
|
||||
- name: Gather routers info
|
||||
openstack.cloud.routers_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ router_name }}"
|
||||
filters:
|
||||
admin_state_up: true
|
||||
register: result
|
||||
|
||||
- name: Verify routers info
|
||||
assert:
|
||||
that:
|
||||
- "result.openstack_routers.0.name == router_name"
|
||||
- (result.openstack_routers.0.interfaces_info|length) == 1
|
||||
- result.openstack_routers.0.interfaces_info.0.ip_address == '10.7.7.1'
|
||||
|
||||
# Admin operation
|
||||
- name: Create external network
|
||||
openstack.cloud.network:
|
||||
@@ -53,12 +177,12 @@
|
||||
when:
|
||||
- network_external
|
||||
|
||||
- name: Create subnet2
|
||||
- name: Create subnet5
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
network_name: "{{ external_network_name }}"
|
||||
name: shade_subnet2
|
||||
name: shade_subnet5
|
||||
cidr: 10.6.6.0/24
|
||||
when:
|
||||
- network_external
|
||||
@@ -88,6 +212,142 @@
|
||||
- "result.openstack_routers.0.name == router_name"
|
||||
- (result.openstack_routers.0.interfaces_info|length) == 1
|
||||
|
||||
- name: Update router (change external fixed ips)
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- shade_subnet1
|
||||
network: "{{ external_network_name }}"
|
||||
external_fixed_ips:
|
||||
- subnet: shade_subnet5
|
||||
ip: 10.6.6.100
|
||||
when:
|
||||
- network_external
|
||||
|
||||
- name: Gather routers info
|
||||
openstack.cloud.routers_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ router_name }}"
|
||||
filters:
|
||||
admin_state_up: true
|
||||
register: result
|
||||
|
||||
- name: Verify routers info
|
||||
assert:
|
||||
that:
|
||||
- "result.openstack_routers.0.name == router_name"
|
||||
- (result.openstack_routers.0.external_gateway_info.external_fixed_ips|length) == 1
|
||||
- result.openstack_routers.0.external_gateway_info.external_fixed_ips.0.ip_address == "10.6.6.100"
|
||||
when:
|
||||
- network_external
|
||||
|
||||
- name: Update router (add external fixed ips)
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- shade_subnet1
|
||||
network: "{{ external_network_name }}"
|
||||
external_fixed_ips:
|
||||
- subnet: shade_subnet5
|
||||
ip: 10.6.6.100
|
||||
- subnet: shade_subnet5
|
||||
ip: 10.6.6.101
|
||||
when:
|
||||
- network_external
|
||||
|
||||
- name: Update router (add external fixed ips) again
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- shade_subnet1
|
||||
network: "{{ external_network_name }}"
|
||||
external_fixed_ips:
|
||||
- subnet: shade_subnet5
|
||||
ip: 10.6.6.100
|
||||
- subnet: shade_subnet5
|
||||
ip: 10.6.6.101
|
||||
when:
|
||||
- network_external
|
||||
register: result
|
||||
|
||||
- name: Assert idempotent module
|
||||
assert:
|
||||
that: result is not changed
|
||||
|
||||
- name: Gather routers info
|
||||
openstack.cloud.routers_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ router_name }}"
|
||||
filters:
|
||||
admin_state_up: true
|
||||
register: result
|
||||
|
||||
- name: Verify routers info
|
||||
assert:
|
||||
that:
|
||||
- "result.openstack_routers.0.name == router_name"
|
||||
- (result.openstack_routers.0.external_gateway_info.external_fixed_ips|length) == 2
|
||||
- result.openstack_routers.0.external_gateway_info.external_fixed_ips|map(attribute='ip_address')|sort|list ==
|
||||
["10.6.6.100", "10.6.6.101"]
|
||||
when:
|
||||
- network_external
|
||||
|
||||
- name: Update router (remove external fixed ips)
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- shade_subnet1
|
||||
network: "{{ external_network_name }}"
|
||||
external_fixed_ips:
|
||||
- subnet: shade_subnet5
|
||||
ip: 10.6.6.101
|
||||
when:
|
||||
- network_external
|
||||
|
||||
- name: Update router (remove external fixed ips) again
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ router_name }}"
|
||||
interfaces:
|
||||
- shade_subnet1
|
||||
network: "{{ external_network_name }}"
|
||||
external_fixed_ips:
|
||||
- subnet: shade_subnet5
|
||||
ip: 10.6.6.101
|
||||
when:
|
||||
- network_external
|
||||
register: result
|
||||
|
||||
- name: Assert idempotent module
|
||||
assert:
|
||||
that: result is not changed
|
||||
|
||||
- name: Gather routers info
|
||||
openstack.cloud.routers_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ router_name }}"
|
||||
filters:
|
||||
admin_state_up: true
|
||||
register: result
|
||||
|
||||
- name: Verify routers info
|
||||
assert:
|
||||
that:
|
||||
- "result.openstack_routers.0.name == router_name"
|
||||
- (result.openstack_routers.0.external_gateway_info.external_fixed_ips|length) == 1
|
||||
- result.openstack_routers.0.external_gateway_info.external_fixed_ips.0.ip_address == "10.6.6.101"
|
||||
when:
|
||||
- network_external
|
||||
|
||||
- name: Delete router
|
||||
openstack.cloud.router:
|
||||
cloud: "{{ cloud }}"
|
||||
@@ -105,6 +365,24 @@
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: shade_subnet2
|
||||
|
||||
- name: Delete subnet3
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: shade_subnet3
|
||||
|
||||
- name: Delete subnet4
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: shade_subnet4
|
||||
|
||||
- name: Delete subnet5
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: shade_subnet5
|
||||
when:
|
||||
- network_external
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
server_network: private
|
||||
server_name: ansible_server
|
||||
server_alt_network: private_alt
|
||||
server_alt_subnet: subnet_alt
|
||||
server_alt_name: ansible_server_alt
|
||||
flavor: m1.tiny
|
||||
floating_ip_pool_name: public
|
||||
boot_volume_size: 5
|
||||
|
||||
@@ -518,3 +518,95 @@
|
||||
that:
|
||||
- info24.openstack_servers.0.status == 'ACTIVE'
|
||||
- server is not changed
|
||||
|
||||
- name: Create network for alternate server
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
name: "{{ server_alt_network }}"
|
||||
state: present
|
||||
|
||||
- name: Create subnet for alternate server
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
network_name: "{{ server_alt_network }}"
|
||||
name: "{{ server_alt_subnet }}"
|
||||
state: present
|
||||
cidr: 192.168.0.0/24
|
||||
|
||||
- name: Create server in alternate project
|
||||
openstack.cloud.server:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
state: present
|
||||
name: "{{ server_alt_name }}"
|
||||
image: "{{ image }}"
|
||||
flavor: "{{ flavor }}"
|
||||
network: "{{ server_alt_network }}"
|
||||
auto_floating_ip: false
|
||||
wait: true
|
||||
register: server_alt
|
||||
|
||||
- name: Get info about server in alternate project
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
server: "{{ server_alt_name }}"
|
||||
register: info25
|
||||
|
||||
- name: Ensure status for server in alternate project is ACTIVE
|
||||
assert:
|
||||
that:
|
||||
- info25.openstack_servers.0.status == 'ACTIVE'
|
||||
|
||||
- name: Try to stop server in alternate project
|
||||
openstack.cloud.server_action:
|
||||
cloud: "{{ cloud }}"
|
||||
server: "{{ server_alt_name }}"
|
||||
action: stop
|
||||
wait: true
|
||||
ignore_errors: true
|
||||
register: server_alt
|
||||
|
||||
- name: Ensure server was not stopped
|
||||
assert:
|
||||
that:
|
||||
- server_alt is failed
|
||||
- server_alt.msg == "Could not find server {{ server_alt_name }}"
|
||||
|
||||
- name: Stop server in alternate project with all_projects=true
|
||||
openstack.cloud.server_action:
|
||||
cloud: "{{ cloud }}"
|
||||
server: "{{ server_alt_name }}"
|
||||
action: stop
|
||||
wait: true
|
||||
all_projects: True
|
||||
register: server_alt
|
||||
|
||||
- name: Get info about server in alternate project
|
||||
openstack.cloud.server_info:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
server: "{{ server_alt_name }}"
|
||||
register: info26
|
||||
|
||||
- name: Ensure status for server is SHUTOFF
|
||||
assert:
|
||||
that:
|
||||
- info26.openstack_servers.0.status == 'SHUTOFF'
|
||||
- server_alt is changed
|
||||
|
||||
- name: Delete server in alternate project
|
||||
openstack.cloud.server:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
state: absent
|
||||
name: "{{ server_alt_name }}"
|
||||
wait: true
|
||||
|
||||
- name: Delete subnet for alternate server
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
name: "{{ server_alt_subnet }}"
|
||||
state: absent
|
||||
|
||||
- name: Delete network for alternate server
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud_alt }}"
|
||||
name: "{{ server_alt_network }}"
|
||||
state: absent
|
||||
|
||||
5
ci/roles/subnet_pool/defaults/main.yml
Normal file
5
ci/roles/subnet_pool/defaults/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
subnet_pool_name: "subnet_pool"
|
||||
address_scope_name: "address_scope"
|
||||
default_prefix_length: 24
|
||||
minimum_prefix_length: 10
|
||||
maximum_prefix_length: 30
|
||||
73
ci/roles/subnet_pool/tasks/main.yaml
Normal file
73
ci/roles/subnet_pool/tasks/main.yaml
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
- name: Create address_scope
|
||||
openstack.cloud.address_scope:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ address_scope_name }}"
|
||||
shared: False
|
||||
ip_version: "4"
|
||||
register: create_address_scope
|
||||
|
||||
- name: Create subnet pool
|
||||
openstack.cloud.subnet_pool:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ subnet_pool_name }}"
|
||||
shared: False
|
||||
address_scope: "{{ address_scope_name }}"
|
||||
prefixes:
|
||||
- 192.168.0.0/24
|
||||
register: create_subnet_pool
|
||||
|
||||
- name: Verify subnet pool
|
||||
assert:
|
||||
that:
|
||||
- create_subnet_pool is successful
|
||||
- create_subnet_pool is changed
|
||||
- create_subnet_pool.subnet_pool.name == subnet_pool_name
|
||||
- create_subnet_pool.subnet_pool.is_shared == False
|
||||
- create_subnet_pool.subnet_pool.is_default == False
|
||||
- create_subnet_pool.subnet_pool.address_scope_id == create_address_scope.address_scope.id
|
||||
- create_subnet_pool.subnet_pool.prefixes == ['192.168.0.0/24']
|
||||
|
||||
|
||||
- name: Update subnet pool
|
||||
openstack.cloud.subnet_pool:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ subnet_pool_name }}"
|
||||
address_scope: "{{ address_scope_name }}"
|
||||
shared: False
|
||||
default_prefix_length: "{{ default_prefix_length }}"
|
||||
minimum_prefix_length: "{{ minimum_prefix_length }}"
|
||||
maximum_prefix_length: "{{ maximum_prefix_length }}"
|
||||
description: "test"
|
||||
prefixes:
|
||||
- 192.168.0.0/24
|
||||
- 192.168.1.0/24
|
||||
register: update_subnet_pool
|
||||
|
||||
- name: Verify updated subnet pool
|
||||
assert:
|
||||
that:
|
||||
- update_subnet_pool is successful
|
||||
- update_subnet_pool is changed
|
||||
- update_subnet_pool.subnet_pool.name == subnet_pool_name
|
||||
- update_subnet_pool.subnet_pool.is_shared == False
|
||||
- update_subnet_pool.subnet_pool.is_default == False
|
||||
- update_subnet_pool.subnet_pool.address_scope_id == create_address_scope.address_scope.id
|
||||
- update_subnet_pool.subnet_pool.prefixes == ['192.168.0.0/23']
|
||||
- update_subnet_pool.subnet_pool.description == 'test'
|
||||
- update_subnet_pool.subnet_pool.default_prefix_length == default_prefix_length
|
||||
- update_subnet_pool.subnet_pool.minimum_prefix_length == minimum_prefix_length
|
||||
- update_subnet_pool.subnet_pool.maximum_prefix_length == maximum_prefix_length
|
||||
|
||||
- name: Delete created subnet pool
|
||||
openstack.cloud.subnet_pool:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ subnet_pool_name }}"
|
||||
state: absent
|
||||
|
||||
|
||||
- name: Delete created address scope
|
||||
openstack.cloud.address_scope:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ address_scope_name }}"
|
||||
state: absent
|
||||
@@ -6,20 +6,22 @@
|
||||
#
|
||||
# tox -e ansible [TAG ...]
|
||||
# or
|
||||
# tox -e ansible -- -c cloudX [TAG ...]
|
||||
# tox -e ansible -- -c cloudX -u cloudY [TAG ...]
|
||||
# or to use the development version of Ansible:
|
||||
# tox -e ansible -- -d -c cloudX [TAG ...]
|
||||
# tox -e ansible -- -d -c cloudX -u cloudY [TAG ...]
|
||||
#
|
||||
# USAGE:
|
||||
# run-ansible-tests.sh -e ENVDIR [-d] [-c CLOUD] [TAG ...]
|
||||
# run-ansible-tests.sh -e ENVDIR [-d] [-c CLOUD] [-u CLOUD_ALT] [TAG ...]
|
||||
#
|
||||
# PARAMETERS:
|
||||
# -d Use Ansible source repo development branch.
|
||||
# -e ENVDIR Directory of the tox environment to use for testing.
|
||||
# -c CLOUD Name of the cloud to use for testing.
|
||||
# Defaults to "devstack-admin".
|
||||
# [TAG ...] Optional list of space-separated tags to control which
|
||||
# modules are tested.
|
||||
# -d Use Ansible source repo development branch.
|
||||
# -e ENVDIR Directory of the tox environment to use for testing.
|
||||
# -c CLOUD Name of the cloud to use for testing.
|
||||
# Defaults to "devstack-admin".
|
||||
# -u CLOUD_ALT Name of another cloud to use for testing.
|
||||
# Defaults to "devstack-alt".
|
||||
# [TAG ...] Optional list of space-separated tags to control which
|
||||
# modules are tested.
|
||||
#
|
||||
# EXAMPLES:
|
||||
# # Run all Ansible tests
|
||||
@@ -31,14 +33,16 @@
|
||||
set -ex
|
||||
|
||||
CLOUD="devstack-admin"
|
||||
CLOUD_ALT="devstack-alt"
|
||||
ENVDIR=
|
||||
USE_DEV=0
|
||||
|
||||
while getopts "c:de:" opt
|
||||
while getopts "c:de:u:" opt
|
||||
do
|
||||
case $opt in
|
||||
d) USE_DEV=1 ;;
|
||||
c) CLOUD=${OPTARG} ;;
|
||||
u) CLOUD_ALT=${OPTARG} ;;
|
||||
e) ENVDIR=${OPTARG} ;;
|
||||
?) echo "Invalid option: -${OPTARG}"
|
||||
exit 1;;
|
||||
@@ -131,8 +135,9 @@ fi
|
||||
SDK_VER=$(python -c "import openstack; print(openstack.version.__version__)")
|
||||
pushd ci/
|
||||
# run tests
|
||||
set -o pipefail
|
||||
ANSIBLE_COLLECTIONS_PATHS=$TEST_COLLECTIONS_PATHS ansible-playbook \
|
||||
-vvv ./run-collection.yml \
|
||||
-e "sdk_version=${SDK_VER} cloud=${CLOUD} image=${IMAGE} ${ANSIBLE_VARS}" \
|
||||
${tag_opt}
|
||||
-e "sdk_version=${SDK_VER} cloud=${CLOUD} cloud_alt=${CLOUD_ALT} image=${IMAGE} ${ANSIBLE_VARS}" \
|
||||
${tag_opt} 2>&1 | sudo tee /opt/stack/logs/test_output.log
|
||||
popd
|
||||
|
||||
@@ -30,17 +30,24 @@
|
||||
when: sdk_version is version(0.44, '>=')
|
||||
- { role: keystone_role, tags: keystone_role }
|
||||
- { role: network, tags: network }
|
||||
- role: neutron_rbac
|
||||
tags:
|
||||
- rbac
|
||||
- neutron_rbac
|
||||
- { role: nova_flavor, tags: nova_flavor }
|
||||
- role: compute_flavor_info
|
||||
tags: nova_flavor
|
||||
- role: nova_services
|
||||
tags: nova_services
|
||||
when: sdk_version is version(0.44, '>=')
|
||||
- { role: object, tags: object }
|
||||
- { role: port, tags: port }
|
||||
- { role: project, tags: project }
|
||||
- role: project_properties
|
||||
tags: project_properties
|
||||
when: sdk_version is version("0.45.01", '>')
|
||||
- { role: router, tags: router }
|
||||
- { role: security_group, tags: security_group }
|
||||
- { role: server, tags: server }
|
||||
- { role: subnet, tags: subnet }
|
||||
- { role: subnet_pool, tags: subnet_pool }
|
||||
- { role: user, tags: user }
|
||||
- { role: user_group, tags: user_group }
|
||||
- { role: user_role, tags: user_role }
|
||||
@@ -50,3 +57,4 @@
|
||||
when: sdk_version is version("0.53.0", '>=')
|
||||
- role: loadbalancer
|
||||
tags: loadbalancer
|
||||
- { role: floating_ip, tags: floating_ip }
|
||||
|
||||
@@ -13,7 +13,7 @@ Naming
|
||||
------
|
||||
|
||||
* This is a collection named ``openstack.cloud``. There is no need for further namespace prefixing.
|
||||
* Name any module that a cloud consumer would expect to use after the logical resource it manages:
|
||||
* Name any module that a cloud consumer would expect to use after the logical resource it manages:
|
||||
``server`` not ``nova``. This naming convention acknowledges that the end user does not care
|
||||
which service manages the resource - that is a deployment detail. For example cloud consumers may
|
||||
not know whether their floating IPs are managed by Nova or Neutron.
|
||||
@@ -24,6 +24,7 @@ Interface
|
||||
* If the resource being managed has an id, it should be returned.
|
||||
* If the resource being managed has an associated object more complex than
|
||||
an id, it should also be returned.
|
||||
* Return format shall be a dictionary or list
|
||||
|
||||
Interoperability
|
||||
----------------
|
||||
@@ -53,7 +54,7 @@ Libraries
|
||||
* All complex cloud interaction or interoperability code should be housed in
|
||||
the `openstacksdk <https://opendev.org/openstack/openstacksdk>`_
|
||||
library.
|
||||
* All OpenStack API interactions should happen via the openstacksdk and not via
|
||||
* All OpenStack API interactions should happen via the openstackSDK and not via
|
||||
OpenStack Client libraries. The OpenStack Client libraries do no have end
|
||||
users as a primary audience, they are for intra-server communication.
|
||||
* All modules should be registered in ``meta/action_groups.yml`` for enabling the
|
||||
|
||||
@@ -33,4 +33,4 @@ build_ignore:
|
||||
- ansible_collections_openstack.egg-info
|
||||
- contrib
|
||||
- changelogs
|
||||
version: 1.5.0
|
||||
version: 1.8.0
|
||||
|
||||
@@ -18,7 +18,7 @@ build_ignore:
|
||||
- ci
|
||||
- galaxy.yml.in
|
||||
- setup.cfg
|
||||
- test-requirements.txt
|
||||
- test-requirements*
|
||||
- tests
|
||||
- tools
|
||||
- tox.ini
|
||||
@@ -29,7 +29,7 @@ build_ignore:
|
||||
- importer_result.json
|
||||
- .tox
|
||||
- .env
|
||||
- .vscode
|
||||
- ansible_collections_openstack.egg-info
|
||||
- contrib
|
||||
- changelogs/.plugin-cache.yaml
|
||||
- changelogs/fragments
|
||||
- changelogs
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
requires_ansible: ">=2.8"
|
||||
action_groups:
|
||||
openstack:
|
||||
- address_scope
|
||||
@@ -8,6 +9,9 @@ action_groups:
|
||||
- baremetal_node
|
||||
- baremetal_node_action
|
||||
- baremetal_node_action
|
||||
- baremetal_node_info
|
||||
- baremetal_port_info
|
||||
- baremetal_port
|
||||
- catalog_endpoint
|
||||
- catalog_service
|
||||
- catalog_service
|
||||
@@ -18,6 +22,8 @@ action_groups:
|
||||
- compute_flavor
|
||||
- compute_flavor_info
|
||||
- compute_flavor_info
|
||||
- compute_service_info
|
||||
- compute_service_info
|
||||
- config
|
||||
- config
|
||||
- dns_zone
|
||||
@@ -93,6 +99,7 @@ action_groups:
|
||||
- stack
|
||||
- subnet
|
||||
- subnets_info
|
||||
- subnet_pool
|
||||
- volume
|
||||
- volume_backup
|
||||
- volume_backup_info
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
name: openstack
|
||||
plugin_type: inventory
|
||||
author: OpenStack Ansible SIG
|
||||
short_description: OpenStack inventory source
|
||||
requirements:
|
||||
@@ -26,7 +25,7 @@ options:
|
||||
show_all:
|
||||
description: toggles showing all vms vs only those with a working IP
|
||||
type: bool
|
||||
default: 'no'
|
||||
default: false
|
||||
inventory_hostname:
|
||||
description: |
|
||||
What to register as the inventory hostname.
|
||||
@@ -42,6 +41,13 @@ options:
|
||||
- name
|
||||
- uuid
|
||||
default: "name"
|
||||
use_names:
|
||||
description: |
|
||||
Use the host's 'name' instead of 'interface_ip' for the 'ansible_host' and
|
||||
'ansible_ssh_host' facts. This might be desired when using jump or
|
||||
bastion hosts and the name is the FQDN of the host.
|
||||
type: bool
|
||||
default: false
|
||||
expand_hostvars:
|
||||
description: |
|
||||
Run extra commands on each host to fill in additional
|
||||
@@ -50,7 +56,7 @@ options:
|
||||
(Note, the default value of this is opposite from the default
|
||||
old openstack.py inventory script's option expand_hostvars)
|
||||
type: bool
|
||||
default: 'no'
|
||||
default: false
|
||||
private:
|
||||
description: |
|
||||
Use the private interface of each server, if it has one, as
|
||||
@@ -58,12 +64,13 @@ options:
|
||||
running ansible inside a server in the cloud and would rather
|
||||
communicate to your servers over the private network.
|
||||
type: bool
|
||||
default: 'no'
|
||||
default: false
|
||||
only_clouds:
|
||||
description: |
|
||||
List of clouds from clouds.yaml to use, instead of using
|
||||
the whole list.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
fail_on_errors:
|
||||
description: |
|
||||
@@ -74,12 +81,12 @@ options:
|
||||
default value of this is opposite from the old openstack.py
|
||||
inventory script's option fail_on_errors)
|
||||
type: bool
|
||||
default: 'no'
|
||||
default: false
|
||||
all_projects:
|
||||
description: |
|
||||
Lists servers from all projects
|
||||
type: bool
|
||||
default: 'no'
|
||||
default: false
|
||||
clouds_yaml_path:
|
||||
description: |
|
||||
Override path to clouds.yaml file. If this value is given it
|
||||
@@ -88,6 +95,7 @@ options:
|
||||
/etc/ansible/openstack.yml to the regular locations documented
|
||||
at https://docs.openstack.org/os-client-config/latest/user/configuration.html#config-files
|
||||
type: list
|
||||
elements: str
|
||||
env:
|
||||
- name: OS_CLIENT_CONFIG_FILE
|
||||
compose:
|
||||
@@ -237,6 +245,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
expand_hostvars = self._config_data.get('expand_hostvars', False)
|
||||
fail_on_errors = self._config_data.get('fail_on_errors', False)
|
||||
all_projects = self._config_data.get('all_projects', False)
|
||||
self.use_names = self._config_data.get('use_names', False)
|
||||
|
||||
source_data = []
|
||||
try:
|
||||
@@ -311,7 +320,11 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
for group_name, group_hosts in groups.items():
|
||||
gname = self.inventory.add_group(group_name)
|
||||
for host in group_hosts:
|
||||
self.inventory.add_child(gname, host)
|
||||
if gname == host:
|
||||
display.vvvv("Same name for host %s and group %s" % (host, gname))
|
||||
self.inventory.add_host(host, gname)
|
||||
else:
|
||||
self.inventory.add_child(gname, host)
|
||||
|
||||
def _get_groups_from_server(self, server_vars, namegroup=True):
|
||||
groups = []
|
||||
@@ -359,10 +372,20 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
def _append_hostvars(self, hostvars, groups, current_host,
|
||||
server, namegroup=False):
|
||||
hostvars[current_host] = dict(
|
||||
ansible_ssh_host=server['interface_ip'],
|
||||
ansible_host=server['interface_ip'],
|
||||
openstack=server)
|
||||
if not self.use_names:
|
||||
hostvars[current_host] = dict(
|
||||
ansible_ssh_host=server['interface_ip'],
|
||||
ansible_host=server['interface_ip'],
|
||||
openstack=server,
|
||||
)
|
||||
|
||||
if self.use_names:
|
||||
hostvars[current_host] = dict(
|
||||
ansible_ssh_host=server['name'],
|
||||
ansible_host=server['name'],
|
||||
openstack=server,
|
||||
)
|
||||
|
||||
self.inventory.add_host(current_host)
|
||||
|
||||
if self.get_option('legacy_groups'):
|
||||
|
||||
@@ -261,6 +261,7 @@ class OpenStackModule:
|
||||
self.results = {'changed': False}
|
||||
self.exit = self.exit_json = self.ansible.exit_json
|
||||
self.fail = self.fail_json = self.ansible.fail_json
|
||||
self.warn = self.ansible.warn
|
||||
self.sdk, self.conn = self.openstack_cloud_from_module()
|
||||
self.check_deprecated_names()
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ options:
|
||||
ip_version:
|
||||
description:
|
||||
- The IP version of the subnet 4 or 6
|
||||
default: 4
|
||||
default: '4'
|
||||
type: str
|
||||
choices: ['4', '6']
|
||||
shared:
|
||||
|
||||
@@ -299,7 +299,10 @@ def main():
|
||||
required=False,
|
||||
type='bool',
|
||||
aliases=['skip_update_of_driver_password'],
|
||||
deprecated_aliases=[dict(name='skip_update_of_driver_password', version='2.0.0')]
|
||||
deprecated_aliases=[dict(
|
||||
name='skip_update_of_driver_password',
|
||||
version='2.0.0',
|
||||
collection_name='openstack.cloud')]
|
||||
),
|
||||
state=dict(required=False, default='present', choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
555
plugins/modules/baremetal_node_info.py
Normal file
555
plugins/modules/baremetal_node_info.py
Normal file
@@ -0,0 +1,555 @@
|
||||
#!/usr/bin/python
|
||||
# coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021 by Red Hat, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: baremetal_node_info
|
||||
short_description: Retrieve information about Bare Metal nodes from OpenStack
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Retrieve information about Bare Metal nodes from OpenStack.
|
||||
options:
|
||||
node:
|
||||
description:
|
||||
- Name or globally unique identifier (UUID) to identify the host.
|
||||
type: str
|
||||
mac:
|
||||
description:
|
||||
- Unique mac address that is used to attempt to identify the host.
|
||||
type: str
|
||||
ironic_url:
|
||||
description:
|
||||
- If noauth mode is utilized, this is required to be set to the
|
||||
endpoint URL for the Ironic API. Use with "auth" and "auth_type"
|
||||
settings set to None.
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Gather information about all baremeal nodes
|
||||
- openstack.cloud.baremetal_node_info:
|
||||
cloud: "devstack"
|
||||
register: result
|
||||
- debug:
|
||||
msg: "{{ result.baremetal_nodes }}"
|
||||
# Gather information about a baremeal node
|
||||
- openstack.cloud.baremetal_node_info:
|
||||
cloud: "devstack"
|
||||
node: "00000000-0000-0000-0000-000000000002"
|
||||
register: result
|
||||
- debug:
|
||||
msg: "{{ result.baremetal_nodes }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
baremetal_nodes:
|
||||
description: Bare Metal node list. A subset of the dictionary keys
|
||||
listed below may be returned, depending on your cloud
|
||||
provider.
|
||||
returned: always, but can be null
|
||||
type: complex
|
||||
contains:
|
||||
allocation_uuid:
|
||||
description: The UUID of the allocation associated with the node.
|
||||
If not null, will be the same as instance_uuid (the
|
||||
opposite is not always true). Unlike instance_uuid,
|
||||
this field is read-only. Please use the Allocation API
|
||||
to remove allocations.
|
||||
returned: success
|
||||
type: str
|
||||
automated_clean:
|
||||
description: Indicates whether the node will perform automated
|
||||
clean or not.
|
||||
returned: success
|
||||
type: bool
|
||||
bios_interface:
|
||||
description: The bios interface to be used for this node.
|
||||
returned: success
|
||||
type: str
|
||||
boot_interface:
|
||||
description: The boot interface for a Node, e.g. "pxe".
|
||||
returned: success
|
||||
type: str
|
||||
boot_mode:
|
||||
description: The boot mode for a node, either "uefi" or "bios"
|
||||
returned: success
|
||||
type: str
|
||||
chassis_uuid:
|
||||
description: UUID of the chassis associated with this Node. May be
|
||||
empty or None.
|
||||
returned: success
|
||||
type: str
|
||||
clean_step:
|
||||
description: The current clean step.
|
||||
returned: success
|
||||
type: str
|
||||
conductor:
|
||||
description: The conductor currently servicing a node. This field
|
||||
is read-only.
|
||||
returned: success
|
||||
type: str
|
||||
conductor_group:
|
||||
description: The conductor group for a node. Case-insensitive
|
||||
string up to 255 characters, containing a-z, 0-9, _,
|
||||
-, and ..
|
||||
returned: success
|
||||
type: str
|
||||
console_enabled:
|
||||
description: Indicates whether console access is enabled or
|
||||
disabled on this node.
|
||||
returned: success
|
||||
type: bool
|
||||
console_interface:
|
||||
description: The console interface for a node, e.g. "no-console".
|
||||
returned: success
|
||||
type: str
|
||||
created_at:
|
||||
description: Bare Metal node created at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
deploy_interface:
|
||||
description: The deploy interface for a node, e.g. "direct".
|
||||
returned: success
|
||||
type: str
|
||||
deploy_step:
|
||||
description: The current deploy step.
|
||||
returned: success
|
||||
type: str
|
||||
driver:
|
||||
description: The name of the driver.
|
||||
returned: success
|
||||
type: str
|
||||
driver_info:
|
||||
description: All the metadata required by the driver to manage this
|
||||
Node. List of fields varies between drivers, and can
|
||||
be retrieved from the
|
||||
/v1/drivers/<DRIVER_NAME>/properties resource.
|
||||
returned: success
|
||||
type: dict
|
||||
driver_internal_info:
|
||||
description: Internal metadata set and stored by the Node's driver.
|
||||
returned: success
|
||||
type: dict
|
||||
extra:
|
||||
description: A set of one or more arbitrary metadata key and value
|
||||
pairs.
|
||||
returned: success
|
||||
type: dict
|
||||
fault:
|
||||
description: The fault indicates the active fault detected by
|
||||
ironic, typically the Node is in "maintenance mode".
|
||||
None means no fault has been detected by ironic.
|
||||
"power failure" indicates ironic failed to retrieve
|
||||
power state from this node. There are other possible
|
||||
types, e.g., "clean failure" and "rescue abort
|
||||
failure".
|
||||
returned: success
|
||||
type: str
|
||||
id:
|
||||
description: The UUID for the resource.
|
||||
returned: success
|
||||
type: str
|
||||
inspect_interface:
|
||||
description: The interface used for node inspection.
|
||||
returned: success
|
||||
type: str
|
||||
instance_info:
|
||||
description: Information used to customize the deployed image. May
|
||||
include root partition size, a base 64 encoded config
|
||||
drive, and other metadata. Note that this field is
|
||||
erased automatically when the instance is deleted
|
||||
(this is done by requesting the Node provision state
|
||||
be changed to DELETED).
|
||||
returned: success
|
||||
type: dict
|
||||
instance_uuid:
|
||||
description: UUID of the Nova instance associated with this Node.
|
||||
returned: success
|
||||
type: str
|
||||
last_error:
|
||||
description: Any error from the most recent (last) transaction that
|
||||
started but failed to finish.
|
||||
returned: success
|
||||
type: str
|
||||
maintenance:
|
||||
description: Whether or not this Node is currently in "maintenance
|
||||
mode". Setting a Node into maintenance mode removes it
|
||||
from the available resource pool and halts some
|
||||
internal automation. This can happen manually (eg, via
|
||||
an API request) or automatically when Ironic detects a
|
||||
hardware fault that prevents communication with the
|
||||
machine.
|
||||
returned: success
|
||||
type: bool
|
||||
maintenance_reason:
|
||||
description: User-settable description of the reason why this Node
|
||||
was placed into maintenance mode
|
||||
returned: success
|
||||
type: str
|
||||
management_interface:
|
||||
description: Interface for out-of-band node management.
|
||||
returned: success
|
||||
type: str
|
||||
name:
|
||||
description: Human-readable identifier for the Node resource. May
|
||||
be undefined. Certain words are reserved.
|
||||
returned: success
|
||||
type: str
|
||||
network_interface:
|
||||
description: Which Network Interface provider to use when plumbing
|
||||
the network connections for this Node.
|
||||
returned: success
|
||||
type: str
|
||||
owner:
|
||||
description: A string or UUID of the tenant who owns the object.
|
||||
returned: success
|
||||
type: str
|
||||
portgroups:
|
||||
description: List of ironic portgroups on this node.
|
||||
returned: success
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
address:
|
||||
description: Physical hardware address of this Portgroup,
|
||||
typically the hardware MAC address.
|
||||
returned: success
|
||||
type: str
|
||||
created_at:
|
||||
description: The UTC date and time when the resource was
|
||||
created, ISO 8601 format.
|
||||
returned: success
|
||||
type: str
|
||||
extra:
|
||||
description: A set of one or more arbitrary metadata key and
|
||||
value pairs.
|
||||
returned: success
|
||||
type: dict
|
||||
id:
|
||||
description: The UUID for the resource.
|
||||
returned: success
|
||||
type: str
|
||||
internal_info:
|
||||
description: Internal metadata set and stored by the Portgroup.
|
||||
This field is read-only.
|
||||
returned: success
|
||||
type: dict
|
||||
is_standalone_ports_supported:
|
||||
description: Indicates whether ports that are members of this
|
||||
portgroup can be used as stand-alone ports.
|
||||
returned: success
|
||||
type: bool
|
||||
mode:
|
||||
description: Mode of the port group. For possible values, refer
|
||||
to https://www.kernel.org/doc/Documentation/networking/bonding.txt.
|
||||
If not specified in a request to create a port
|
||||
group, it will be set to the value of the
|
||||
[DEFAULT]default_portgroup_mode configuration
|
||||
option. When set, can not be removed from the port
|
||||
group.
|
||||
returned: success
|
||||
type: str
|
||||
name:
|
||||
description: Human-readable identifier for the Portgroup
|
||||
resource. May be undefined.
|
||||
returned: success
|
||||
type: str
|
||||
node_id:
|
||||
description: UUID of the Node this resource belongs to.
|
||||
returned: success
|
||||
type: str
|
||||
ports:
|
||||
description: List of port UUID's of ports belonging to this
|
||||
portgroup.
|
||||
returned: success
|
||||
type: list
|
||||
properties:
|
||||
description: Key/value properties related to the port group's
|
||||
configuration.
|
||||
returned: success
|
||||
type: dict
|
||||
updated_at:
|
||||
description: The UTC date and time when the resource was
|
||||
updated, ISO 8601 format. May be "null".
|
||||
returned: success
|
||||
type: str
|
||||
ports:
|
||||
description: List of ironic ports on this node.
|
||||
returned: success
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
address:
|
||||
description: Physical hardware address of this network Port,
|
||||
typically the hardware MAC address.
|
||||
returned: success
|
||||
type: str
|
||||
created_at:
|
||||
description: The UTC date and time when the resource was
|
||||
created, ISO 8601 format.
|
||||
returned: success
|
||||
type: str
|
||||
extra:
|
||||
description: A set of one or more arbitrary metadata key and
|
||||
value pairs.
|
||||
returned: success
|
||||
type: dict
|
||||
id:
|
||||
description: The UUID for the resource.
|
||||
returned: success
|
||||
type: str
|
||||
internal_info:
|
||||
description: Internal metadata set and stored by the Port. This
|
||||
field is read-only.
|
||||
returned: success
|
||||
type: dict
|
||||
local_link_connection:
|
||||
description: The Port binding profile. If specified, must
|
||||
contain switch_id (only a MAC address or an
|
||||
OpenFlow based datapath_id of the switch are
|
||||
accepted in this field) and port_id (identifier of
|
||||
the physical port on the switch to which node's
|
||||
port is connected to) fields. switch_info is an
|
||||
optional string field to be used to store any
|
||||
vendor-specific information.
|
||||
returned: success
|
||||
type: dict
|
||||
name:
|
||||
description: The name of the resource.
|
||||
returned: success
|
||||
type: str
|
||||
node_uuid:
|
||||
description: UUID of the Node this resource belongs to.
|
||||
returned: success
|
||||
type: str
|
||||
physical_network:
|
||||
description: The name of the physical network to which a port
|
||||
is connected. May be empty.
|
||||
returned: success
|
||||
type: str
|
||||
portgroup_uuid:
|
||||
description: UUID of the Portgroup this resource belongs to.
|
||||
returned: success
|
||||
type: str
|
||||
pxe_enabled:
|
||||
description: Indicates whether PXE is enabled or disabled on
|
||||
the Port.
|
||||
returned: success
|
||||
type: str
|
||||
updated_at:
|
||||
description: The UTC date and time when the resource was
|
||||
updated, ISO 8601 format. May be "null".
|
||||
returned: success
|
||||
type: str
|
||||
uuid:
|
||||
description: The UUID for the resource.
|
||||
returned: success
|
||||
type: str
|
||||
power_interface:
|
||||
description: Interface used for performing power actions on the
|
||||
node, e.g. "ipmitool".
|
||||
returned: success
|
||||
type: str
|
||||
power_state:
|
||||
description: The current power state of this Node. Usually, "power
|
||||
on" or "power off", but may be "None" if Ironic is
|
||||
unable to determine the power state (eg, due to
|
||||
hardware failure).
|
||||
returned: success
|
||||
type: str
|
||||
properties:
|
||||
description: Physical characteristics of this Node. Populated by
|
||||
ironic-inspector during inspection. May be edited via
|
||||
the REST API at any time.
|
||||
returned: success
|
||||
type: dict
|
||||
protected:
|
||||
description: Whether the node is protected from undeploying,
|
||||
rebuilding and deletion.
|
||||
returned: success
|
||||
type: bool
|
||||
protected_reason:
|
||||
description: The reason the node is marked as protected.
|
||||
returned: success
|
||||
type: str
|
||||
provision_state:
|
||||
description: The current provisioning state of this Node.
|
||||
returned: success
|
||||
type: str
|
||||
raid_config:
|
||||
description: Represents the current RAID configuration of the node.
|
||||
Introduced with the cleaning feature.
|
||||
returned: success
|
||||
type: dict
|
||||
raid_interface:
|
||||
description: Interface used for configuring RAID on this node.
|
||||
returned: success
|
||||
type: str
|
||||
rescue_interface:
|
||||
description: The interface used for node rescue, e.g. "no-rescue".
|
||||
returned: success
|
||||
type: str
|
||||
reservation:
|
||||
description: The name of an Ironic Conductor host which is holding
|
||||
a lock on this node, if a lock is held. Usually
|
||||
"null", but this field can be useful for debugging.
|
||||
returned: success
|
||||
type: str
|
||||
resource_class:
|
||||
description: A string which can be used by external schedulers to
|
||||
identify this Node as a unit of a specific type of
|
||||
resource. For more details, see
|
||||
https://docs.openstack.org/ironic/latest/install/configure-nova-flavors.html
|
||||
returned: success
|
||||
type: str
|
||||
retired:
|
||||
description: Whether the node is retired and can hence no longer be
|
||||
provided, i.e. move from manageable to available, and
|
||||
will end up in manageable after cleaning (rather than
|
||||
available).
|
||||
returned: success
|
||||
type: bool
|
||||
retired_reason:
|
||||
description: The reason the node is marked as retired.
|
||||
returned: success
|
||||
type: str
|
||||
secure_boot:
|
||||
description: Indicates whether node is currently booted with
|
||||
secure_boot turned on.
|
||||
returned: success
|
||||
type: bool
|
||||
storage_interface:
|
||||
description: Interface used for attaching and detaching volumes on
|
||||
this node, e.g. "cinder".
|
||||
returned: success
|
||||
type: str
|
||||
target_power_state:
|
||||
description: If a power state transition has been requested, this
|
||||
field represents the requested (ie, "target") state,
|
||||
either "power on" or "power off".
|
||||
returned: success
|
||||
type: str
|
||||
target_provision_state:
|
||||
description: If a provisioning action has been requested, this
|
||||
field represents the requested (ie, "target") state.
|
||||
Note that a Node may go through several states during
|
||||
its transition to this target state. For instance,
|
||||
when requesting an instance be deployed to an
|
||||
AVAILABLE Node, the Node may go through the following
|
||||
state change progression, AVAILABLE -> DEPLOYING ->
|
||||
DEPLOYWAIT -> DEPLOYING -> ACTIVE
|
||||
returned: success
|
||||
type: str
|
||||
target_raid_config:
|
||||
description: Represents the requested RAID configuration of the
|
||||
node, which will be applied when the Node next
|
||||
transitions through the CLEANING state. Introduced
|
||||
with the cleaning feature.
|
||||
returned: success
|
||||
type: dict
|
||||
traits:
|
||||
description: List of traits for this node.
|
||||
returned: success
|
||||
type: list
|
||||
updated_at:
|
||||
description: Bare Metal node updated at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
uuid:
|
||||
description: The UUID for the resource.
|
||||
returned: success
|
||||
type: str
|
||||
vendor_interface:
|
||||
description: Interface for vendor-specific functionality on this
|
||||
node, e.g. "no-vendor".
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.ironic import (
|
||||
IronicModule,
|
||||
ironic_argument_spec,
|
||||
)
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
|
||||
openstack_module_kwargs,
|
||||
openstack_cloud_from_module
|
||||
)
|
||||
|
||||
|
||||
def cleanup_node_properties(machine, cloud):
|
||||
# states are links, not useful
|
||||
machine.pop('states', None)
|
||||
|
||||
for port in machine.ports:
|
||||
# links are not useful
|
||||
port.pop('links', None)
|
||||
# redundant, location is in on machine as well
|
||||
port.pop('location', None)
|
||||
|
||||
for portgroup in machine.portgroups:
|
||||
# links are not useful
|
||||
portgroup.pop('links', None)
|
||||
# redundant, location is in on machine as well
|
||||
portgroup.pop('location', None)
|
||||
# links to ports are not useful, replace with list of port uuid's
|
||||
portgroup['ports'] = [x.id for x in list(
|
||||
cloud.baremetal.ports(portgroup=portgroup['id']))]
|
||||
|
||||
|
||||
def get_ports_and_portgroups(cloud, machine):
|
||||
machine.ports = cloud.list_nics_for_machine(machine.uuid)
|
||||
machine.portgroups = [dict(x) for x in
|
||||
list(cloud.baremetal.port_groups(node=machine.uuid,
|
||||
details=True))]
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ironic_argument_spec(
|
||||
node=dict(required=False),
|
||||
mac=dict(required=False),
|
||||
)
|
||||
module_kwargs = openstack_module_kwargs()
|
||||
module_kwargs['supports_check_mode'] = True
|
||||
|
||||
module = IronicModule(argument_spec, **module_kwargs)
|
||||
|
||||
machine = None
|
||||
machines = list()
|
||||
|
||||
sdk, cloud = openstack_cloud_from_module(module)
|
||||
try:
|
||||
if module.params['node']:
|
||||
machine = cloud.get_machine(module.params['node'])
|
||||
elif module.params['mac']:
|
||||
machine = cloud.get_machine_by_mac(module.params['mac'])
|
||||
|
||||
# Fail if node not found
|
||||
if (module.params['node'] or module.params['mac']) and not machine:
|
||||
module.fail_json(msg='The baremetal node was not found')
|
||||
|
||||
if machine:
|
||||
machines.append(machine)
|
||||
else:
|
||||
machines = cloud.list_machines()
|
||||
|
||||
for machine in machines:
|
||||
get_ports_and_portgroups(cloud, machine)
|
||||
cleanup_node_properties(machine, cloud)
|
||||
|
||||
module.exit_json(changed=False, baremetal_nodes=machines)
|
||||
except sdk.exceptions.OpenStackCloudException as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
373
plugins/modules/baremetal_port.py
Normal file
373
plugins/modules/baremetal_port.py
Normal file
@@ -0,0 +1,373 @@
|
||||
#!/usr/bin/python
|
||||
# coding: utf-8 -*-
|
||||
# Copyright (c) 2021 by Red Hat, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: baremetal_port
|
||||
short_description: Create/Delete Bare Metal port Resources from OpenStack
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Create, Update and Remove ironic ports from OpenStack.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Indicates desired state of the resource
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
uuid:
|
||||
description:
|
||||
- globally unique identifier (UUID) to be given to the resource. Will
|
||||
be auto-generated if not specified.
|
||||
type: str
|
||||
node:
|
||||
description:
|
||||
- UUID or Name of the Node this resource belongs to.
|
||||
type: str
|
||||
address:
|
||||
description:
|
||||
- Physical hardware address of this network Port, typically the
|
||||
hardware MAC address.
|
||||
type: str
|
||||
portgroup:
|
||||
description:
|
||||
- UUID or Name of the Portgroup this resource belongs to.
|
||||
type: str
|
||||
local_link_connection:
|
||||
description:
|
||||
- The Port binding profile.
|
||||
type: dict
|
||||
suboptions:
|
||||
switch_id:
|
||||
description:
|
||||
- A MAC address or an OpenFlow based datapath_id of the switch.
|
||||
type: str
|
||||
port_id:
|
||||
description:
|
||||
- Identifier of the physical port on the switch to which node's
|
||||
port is connected to.
|
||||
type: str
|
||||
switch_info:
|
||||
description:
|
||||
- An optional string field to be used to store any vendor-specific
|
||||
information.
|
||||
type: str
|
||||
is_pxe_enabled:
|
||||
description:
|
||||
- Whether PXE should be enabled or disabled on the Port.
|
||||
type: bool
|
||||
physical_network:
|
||||
description:
|
||||
- The name of the physical network to which a port is connected.
|
||||
type: str
|
||||
extra:
|
||||
description:
|
||||
- A set of one or more arbitrary metadata key and value pairs.
|
||||
type: dict
|
||||
ironic_url:
|
||||
description:
|
||||
- If noauth mode is utilized, this is required to be set to the
|
||||
endpoint URL for the Ironic API. Use with "auth" and "auth_type"
|
||||
settings set to None.
|
||||
type: str
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create Bare Metal port
|
||||
- name: Create Bare Metal port
|
||||
openstack.cloud.baremetal_port:
|
||||
cloud: devstack
|
||||
state: present
|
||||
node: bm-0
|
||||
address: fa:16:3e:aa:aa:aa
|
||||
pxe_enabled: True
|
||||
local_link_connection:
|
||||
switch_id: 0a:1b:2c:3d:4e:5f
|
||||
port_id: Ethernet3/1
|
||||
switch_info: switch1
|
||||
extra:
|
||||
something: extra
|
||||
physical_network: datacenter
|
||||
register: result
|
||||
# Delete Bare Metal port
|
||||
- name: Delete Bare Metal port
|
||||
openstack.cloud.baremetal_port:
|
||||
cloud: devstack
|
||||
state: absent
|
||||
address: fa:16:3e:aa:aa:aa
|
||||
register: result
|
||||
# Update Bare Metal port
|
||||
- name: Update Bare Metal port
|
||||
openstack.cloud.baremetal_port:
|
||||
cloud: devstack
|
||||
state: present
|
||||
uuid: 1a85ebca-22bf-42eb-ad9e-f640789b8098
|
||||
pxe_enabled: False
|
||||
local_link_connection:
|
||||
switch_id: a0:b1:c2:d3:e4:f5
|
||||
port_id: Ethernet4/12
|
||||
switch_info: switch2
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
id:
|
||||
description: Unique UUID of the port.
|
||||
returned: always, but can be null
|
||||
type: str
|
||||
result:
|
||||
description: A short text describing the result.
|
||||
returned: success
|
||||
type: str
|
||||
changes:
|
||||
description: Map showing from -> to values for properties that was changed
|
||||
after port update.
|
||||
returned: success
|
||||
type: dict
|
||||
port:
|
||||
description: A port dictionary, subset of the dictionary keys listed below
|
||||
may be returned, depending on your cloud provider.
|
||||
returned: success
|
||||
type: complex
|
||||
contains:
|
||||
address:
|
||||
description: Physical hardware address of this network Port,
|
||||
typically the hardware MAC address.
|
||||
returned: success
|
||||
type: str
|
||||
created_at:
|
||||
description: Bare Metal port created at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
extra:
|
||||
description: A set of one or more arbitrary metadata key and value
|
||||
pairs.
|
||||
returned: success
|
||||
type: dict
|
||||
id:
|
||||
description: The UUID for the Baremetal Port resource.
|
||||
returned: success
|
||||
type: str
|
||||
internal_info:
|
||||
description: Internal metadata set and stored by the Port. This
|
||||
field is read-only.
|
||||
returned: success
|
||||
type: dict
|
||||
is_pxe_enabled:
|
||||
description: Whether PXE is enabled or disabled on the Port.
|
||||
returned: success
|
||||
type: bool
|
||||
local_link_connection:
|
||||
description: The Port binding profile. If specified, must contain
|
||||
switch_id (only a MAC address or an OpenFlow based
|
||||
datapath_id of the switch are accepted in this field
|
||||
and port_id (identifier of the physical port on the
|
||||
switch to which node's port is connected to) fields.
|
||||
switch_info is an optional string field to be used to
|
||||
store any vendor-specific information.
|
||||
returned: success
|
||||
type: dict
|
||||
location:
|
||||
description: Cloud location of this resource (cloud, project,
|
||||
region, zone)
|
||||
returned: success
|
||||
type: dict
|
||||
name:
|
||||
description: Bare Metal port name.
|
||||
returned: success
|
||||
type: str
|
||||
node_id:
|
||||
description: UUID of the Bare Metal Node this resource belongs to.
|
||||
returned: success
|
||||
type: str
|
||||
physical_network:
|
||||
description: The name of the physical network to which a port is
|
||||
connected.
|
||||
returned: success
|
||||
type: str
|
||||
port_group_id:
|
||||
description: UUID of the Portgroup this resource belongs to.
|
||||
returned: success
|
||||
type: str
|
||||
updated_at:
|
||||
description: Bare Metal port updated at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.ironic import (
|
||||
IronicModule,
|
||||
ironic_argument_spec,
|
||||
)
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
|
||||
openstack_module_kwargs,
|
||||
openstack_cloud_from_module
|
||||
)
|
||||
|
||||
_PROP_TO_ATTR_MAP = {
|
||||
'pxe_enabled': 'is_pxe_enabled',
|
||||
'address': 'address',
|
||||
'extra': 'extra',
|
||||
'local_link_connection': 'local_link_connection',
|
||||
'physical_network': 'physical_network',
|
||||
'node_uuid': 'node_id',
|
||||
'portgroup_uuid': 'port_group_id',
|
||||
'uuid': 'id',
|
||||
}
|
||||
|
||||
|
||||
def find_port(module, cloud):
|
||||
port = None
|
||||
if module.params['uuid']:
|
||||
port = cloud.baremetal.find_port(module.params['uuid'])
|
||||
elif module.params['address']:
|
||||
ports = list(cloud.baremetal.ports(address=module.params['address'],
|
||||
details=True))
|
||||
if ports and len(ports) == 1:
|
||||
port = ports[0]
|
||||
elif len(ports) > 1:
|
||||
module.fail_json(
|
||||
msg="Multiple ports with address {address} found. A uuid must "
|
||||
"be defined in order to identify the correct port"
|
||||
.format(address=module.params['address']))
|
||||
|
||||
return port
|
||||
|
||||
|
||||
def add_port(module, cloud):
|
||||
port = find_port(module, cloud)
|
||||
if port:
|
||||
update_port(module, cloud, port=port)
|
||||
|
||||
if not module.params['node'] or not module.params['address']:
|
||||
module.fail_json(
|
||||
msg="A Bare Metal node (name or uuid) and an address is required "
|
||||
"to create a port")
|
||||
|
||||
machine = cloud.get_machine(module.params['node'])
|
||||
if not machine:
|
||||
module.fail_json(
|
||||
msg="Bare Metal node {node} could not be found".format(
|
||||
node=module.params['node']))
|
||||
|
||||
module.params['node_uuid'] = machine.id
|
||||
props = {k: module.params[k] for k in _PROP_TO_ATTR_MAP.keys()
|
||||
if k in module.params}
|
||||
port = cloud.baremetal.create_port(**props)
|
||||
port_dict = port.to_dict()
|
||||
port_dict.pop('links', None)
|
||||
module.exit_json(
|
||||
changed=True,
|
||||
result="Port successfully created",
|
||||
changes=None,
|
||||
port=port_dict,
|
||||
id=port_dict['id'])
|
||||
|
||||
|
||||
def update_port(module, cloud, port=None):
|
||||
if not port:
|
||||
port = find_port(module, cloud)
|
||||
|
||||
if module.params['node']:
|
||||
machine = cloud.get_machine(module.params['node'])
|
||||
if machine:
|
||||
module.params['node_uuid'] = machine.id
|
||||
|
||||
old_props = {k: port[v] for k, v in _PROP_TO_ATTR_MAP.items()}
|
||||
new_props = {k: module.params[k] for k in _PROP_TO_ATTR_MAP.keys()
|
||||
if k in module.params and module.params[k] is not None}
|
||||
prop_diff = {k: new_props[k] for k in _PROP_TO_ATTR_MAP.keys()
|
||||
if k in new_props and old_props[k] != new_props[k]}
|
||||
|
||||
if not prop_diff:
|
||||
port_dict = port.to_dict()
|
||||
port_dict.pop('links', None)
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
result="No port update required",
|
||||
changes=None,
|
||||
port=port_dict,
|
||||
id=port_dict['id'])
|
||||
|
||||
port = cloud.baremetal.update_port(port.id, **prop_diff)
|
||||
port_dict = port.to_dict()
|
||||
port_dict.pop('links', None)
|
||||
module.exit_json(
|
||||
changed=True,
|
||||
result="Port successfully updated",
|
||||
changes={k: {'to': new_props[k], 'from': old_props[k]}
|
||||
for k in prop_diff},
|
||||
port=port_dict,
|
||||
id=port_dict['id'])
|
||||
|
||||
|
||||
def remove_port(module, cloud):
|
||||
if not module.params['uuid'] and not module.params['address']:
|
||||
module.fail_json(
|
||||
msg="A uuid or an address value must be defined in order to "
|
||||
"remove a port.")
|
||||
if module.params['uuid']:
|
||||
port = cloud.baremetal.delete_port(module.params['uuid'])
|
||||
if not port:
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
result="Port not found",
|
||||
changes=None,
|
||||
id=module.params['uuid'])
|
||||
else:
|
||||
port = find_port(module, cloud)
|
||||
if not port:
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
result="Port not found",
|
||||
changes=None,
|
||||
id=None)
|
||||
port = cloud.baremetal.delete_port(port.id)
|
||||
|
||||
module.exit_json(
|
||||
changed=True,
|
||||
result="Port successfully removed",
|
||||
changes=None,
|
||||
id=port.id)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ironic_argument_spec(
|
||||
uuid=dict(required=False),
|
||||
node=dict(required=False),
|
||||
address=dict(required=False),
|
||||
portgroup=dict(required=False),
|
||||
local_link_connection=dict(required=False, type='dict'),
|
||||
is_pxe_enabled=dict(required=False, type='bool'),
|
||||
physical_network=dict(required=False),
|
||||
extra=dict(required=False, type='dict'),
|
||||
state=dict(required=False,
|
||||
default='present',
|
||||
choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
module_kwargs = openstack_module_kwargs()
|
||||
module = IronicModule(argument_spec, **module_kwargs)
|
||||
|
||||
module.params['pxe_enabled'] = module.params.pop('is_pxe_enabled', None)
|
||||
|
||||
sdk, cloud = openstack_cloud_from_module(module)
|
||||
try:
|
||||
if module.params['state'] == 'present':
|
||||
add_port(module, cloud)
|
||||
|
||||
if module.params['state'] == 'absent':
|
||||
remove_port(module, cloud)
|
||||
|
||||
except sdk.exceptions.OpenStackCloudException as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
208
plugins/modules/baremetal_port_info.py
Normal file
208
plugins/modules/baremetal_port_info.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/python
|
||||
# coding: utf-8 -*-
|
||||
# Copyright (c) 2021 by Red Hat, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: baremetal_port_info
|
||||
short_description: Retrieve information about Bare Metal ports from OpenStack
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Retrieve information about Bare Metal ports from OpenStack.
|
||||
options:
|
||||
uuid:
|
||||
description:
|
||||
- Name or globally unique identifier (UUID) to identify the port.
|
||||
type: str
|
||||
address:
|
||||
description:
|
||||
- Physical hardware address of this network Port, typically the
|
||||
hardware MAC address.
|
||||
type: str
|
||||
node:
|
||||
description:
|
||||
- Name or globally unique identifier (UUID) to identify a Baremetal
|
||||
Node.
|
||||
type: str
|
||||
ironic_url:
|
||||
description:
|
||||
- If noauth mode is utilized, this is required to be set to the
|
||||
endpoint URL for the Ironic API. Use with "auth" and "auth_type"
|
||||
settings set to None.
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Gather information about all baremetal ports
|
||||
- openstack.cloud.baremetal_port_info:
|
||||
cloud: devstack
|
||||
register: result
|
||||
# Gather information about a baremetal port by address
|
||||
- openstack.cloud.baremetal_port_info:
|
||||
cloud: devstack
|
||||
address: fa:16:3e:aa:aa:aa
|
||||
register: result
|
||||
# Gather information about a baremetal port by address
|
||||
- openstack.cloud.baremetal_port_info:
|
||||
cloud: devstack
|
||||
uuid: a2b6bd99-77b9-43f0-9ddc-826568e68dec
|
||||
register: result
|
||||
# Gather information about a baremetal ports associated with a baremetal node
|
||||
- openstack.cloud.baremetal_port_info:
|
||||
cloud: devstack
|
||||
node: bm-0
|
||||
register: result
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
baremetal_ports:
|
||||
description: Bare Metal port list. A subset of the dictionary keys
|
||||
listed below may be returned, depending on your cloud
|
||||
provider.
|
||||
returned: always, but can be null
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
address:
|
||||
description: Physical hardware address of this network Port,
|
||||
typically the hardware MAC address.
|
||||
returned: success
|
||||
type: str
|
||||
created_at:
|
||||
description: Bare Metal port created at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
extra:
|
||||
description: A set of one or more arbitrary metadata key and
|
||||
value pairs.
|
||||
returned: success
|
||||
type: dict
|
||||
id:
|
||||
description: The UUID for the Baremetal Port resource.
|
||||
returned: success
|
||||
type: str
|
||||
internal_info:
|
||||
description: Internal metadata set and stored by the Port. This
|
||||
field is read-only.
|
||||
returned: success
|
||||
type: dict
|
||||
is_pxe_enabled:
|
||||
description: Whether PXE is enabled or disabled on the Port.
|
||||
returned: success
|
||||
type: bool
|
||||
local_link_connection:
|
||||
description: The Port binding profile.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
switch_id:
|
||||
description: A MAC address or an OpenFlow based datapath_id of
|
||||
the switch.
|
||||
type: str
|
||||
port_id:
|
||||
description: Identifier of the physical port on the switch to
|
||||
which node's port is connected to.
|
||||
type: str
|
||||
switch_info:
|
||||
description: An optional string field to be used to store any
|
||||
vendor-specific information.
|
||||
type: str
|
||||
location:
|
||||
description: Cloud location of this resource (cloud, project,
|
||||
region, zone)
|
||||
returned: success
|
||||
type: dict
|
||||
name:
|
||||
description: Bare Metal port name.
|
||||
returned: success
|
||||
type: str
|
||||
node_id:
|
||||
description: UUID of the Bare Metal Node this resource belongs to.
|
||||
returned: success
|
||||
type: str
|
||||
physical_network:
|
||||
description: The name of the physical network to which a port is
|
||||
connected.
|
||||
returned: success
|
||||
type: str
|
||||
port_group_id:
|
||||
description: UUID of the Portgroup this resource belongs to.
|
||||
returned: success
|
||||
type: str
|
||||
updated_at:
|
||||
description: Bare Metal port updated at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.ironic import (
|
||||
IronicModule,
|
||||
ironic_argument_spec,
|
||||
)
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
|
||||
openstack_module_kwargs,
|
||||
openstack_cloud_from_module
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ironic_argument_spec(
|
||||
uuid=dict(required=False),
|
||||
address=dict(required=False),
|
||||
node=dict(required=False),
|
||||
)
|
||||
module_kwargs = openstack_module_kwargs()
|
||||
module_kwargs['supports_check_mode'] = True
|
||||
module = IronicModule(argument_spec, **module_kwargs)
|
||||
|
||||
ports = list()
|
||||
sdk, cloud = openstack_cloud_from_module(module)
|
||||
try:
|
||||
if module.params['uuid']:
|
||||
port = cloud.baremetal.find_port(module.params['uuid'])
|
||||
if not port:
|
||||
module.fail_json(
|
||||
msg='Baremetal port with uuid {uuid} was not found'
|
||||
.format(uuid=module.params['uuid']))
|
||||
ports.append(port)
|
||||
|
||||
elif module.params['address']:
|
||||
ports = list(
|
||||
cloud.baremetal.ports(address=module.params['address'],
|
||||
details=True))
|
||||
if not ports:
|
||||
module.fail_json(
|
||||
msg='Baremetal port with address {address} was not found'
|
||||
.format(address=module.params['address']))
|
||||
|
||||
elif module.params['node']:
|
||||
machine = cloud.get_machine(module.params['node'])
|
||||
if not machine:
|
||||
module.fail_json(
|
||||
msg='Baremetal node {node} was not found'
|
||||
.format(node=module.params['node']))
|
||||
ports = list(
|
||||
cloud.baremetal.ports(node_uuid=machine.uuid, details=True))
|
||||
|
||||
else:
|
||||
ports = list(cloud.baremetal.ports(details=True))
|
||||
|
||||
# Convert ports to dictionaries and cleanup properties
|
||||
ports = [port.to_dict() for port in ports]
|
||||
for port in ports:
|
||||
# links are not useful
|
||||
port.pop('links', None)
|
||||
|
||||
module.exit_json(changed=False, baremetal_ports=ports)
|
||||
except sdk.exceptions.OpenStackCloudException as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -220,9 +220,12 @@ class ComputeFlavorModule(OpenStackModule):
|
||||
if self.params[param_key] != flavor[param_key]:
|
||||
require_update = True
|
||||
break
|
||||
|
||||
flavorid = self.params['flavorid']
|
||||
if flavor and require_update:
|
||||
self.conn.delete_flavor(name)
|
||||
old_extra_specs = {}
|
||||
if flavorid == 'auto':
|
||||
flavorid = flavor['id']
|
||||
flavor = None
|
||||
|
||||
if not flavor:
|
||||
@@ -231,7 +234,7 @@ class ComputeFlavorModule(OpenStackModule):
|
||||
ram=self.params['ram'],
|
||||
vcpus=self.params['vcpus'],
|
||||
disk=self.params['disk'],
|
||||
flavorid=self.params['flavorid'],
|
||||
flavorid=flavorid,
|
||||
ephemeral=self.params['ephemeral'],
|
||||
swap=self.params['swap'],
|
||||
rxtx_factor=self.params['rxtx_factor'],
|
||||
|
||||
@@ -126,6 +126,29 @@ openstack_flavors:
|
||||
returned: success
|
||||
type: str
|
||||
sample: "tiny"
|
||||
description:
|
||||
description: Description of the flavor
|
||||
returned: success
|
||||
type: str
|
||||
sample: "Small flavor"
|
||||
is_disabled:
|
||||
description: Wether the flavor is enabled or not
|
||||
returned: success
|
||||
type: bool
|
||||
sample: False
|
||||
rxtx_factor:
|
||||
description: Factor to be multiplied by the rxtx_base property of
|
||||
the network it is attached to in order to have a
|
||||
different bandwidth cap.
|
||||
returned: success
|
||||
type: float
|
||||
sample: 1.0
|
||||
extra_specs:
|
||||
description: Optional parameters to configure different flavors
|
||||
options.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'hw_rng:allowed': True}"
|
||||
disk:
|
||||
description: Size of local disk, in GB.
|
||||
returned: success
|
||||
@@ -174,7 +197,8 @@ class ComputeFlavorInfoModule(OpenStackModule):
|
||||
['name', 'ram'],
|
||||
['name', 'vcpus'],
|
||||
['name', 'ephemeral']
|
||||
]
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
deprecated_names = ('openstack.cloud.compute_flavor_facts')
|
||||
@@ -195,16 +219,22 @@ class ComputeFlavorInfoModule(OpenStackModule):
|
||||
filters['ephemeral'] = ephemeral
|
||||
|
||||
if name:
|
||||
flavors = self.conn.search_flavors(filters={'name': name})
|
||||
# extra_specs are exposed in the flavor representation since Rocky, so we do not
|
||||
# need get_extra_specs=True which is not available in OpenStack SDK 0.36 (Train)
|
||||
# Ref.: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html
|
||||
flavor = self.conn.compute.find_flavor(name)
|
||||
flavors = [flavor] if flavor else []
|
||||
|
||||
else:
|
||||
flavors = self.conn.list_flavors()
|
||||
flavors = list(self.conn.compute.flavors())
|
||||
if filters:
|
||||
flavors = self.conn.range_search(flavors, filters)
|
||||
|
||||
if limit is not None:
|
||||
flavors = flavors[:limit]
|
||||
|
||||
# Transform entries to dict
|
||||
flavors = [flavor.to_dict(computed=True) for flavor in flavors]
|
||||
self.exit_json(changed=False, openstack_flavors=flavors)
|
||||
|
||||
|
||||
|
||||
123
plugins/modules/compute_service_info.py
Normal file
123
plugins/modules/compute_service_info.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2016 Hewlett-Packard Enterprise Corporation
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: compute_service_info
|
||||
short_description: Retrieve information about one or more OpenStack compute services
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Retrieve information about nova compute services
|
||||
options:
|
||||
binary:
|
||||
description:
|
||||
- Filter by service binary type
|
||||
type: str
|
||||
host:
|
||||
description:
|
||||
- Filter by service host
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Gather information about compute services
|
||||
- openstack.cloud.compute_service_info:
|
||||
cloud: awesomecloud
|
||||
binary: "nova-compute"
|
||||
host: "localhost"
|
||||
register: result
|
||||
- openstack.cloud.compute_service_info:
|
||||
cloud: awesomecloud
|
||||
register: result
|
||||
- debug:
|
||||
msg: "{{ result.openstack_compute_services }}"
|
||||
'''
|
||||
|
||||
|
||||
RETURN = '''
|
||||
openstack_compute_services:
|
||||
description: has all the OpenStack information about compute services
|
||||
returned: always, but can be null
|
||||
type: complex
|
||||
contains:
|
||||
id:
|
||||
description: Unique UUID.
|
||||
returned: success
|
||||
type: str
|
||||
binary:
|
||||
description: The binary name of the service.
|
||||
returned: success
|
||||
type: str
|
||||
host:
|
||||
description: The name of the host.
|
||||
returned: success
|
||||
type: str
|
||||
disabled_reason:
|
||||
description: The reason why the service is disabled
|
||||
returned: success
|
||||
type: str
|
||||
availability_zone:
|
||||
description: The availability zone name.
|
||||
returned: success
|
||||
type: str
|
||||
is_forced_down:
|
||||
description: If the service has been forced down or nova-compute
|
||||
returned: success
|
||||
type: bool
|
||||
name:
|
||||
description: Service name
|
||||
returned: success
|
||||
type: str
|
||||
status:
|
||||
description: The status of the service. One of enabled or disabled.
|
||||
returned: success
|
||||
type: str
|
||||
state:
|
||||
description: The state of the service. One of up or down.
|
||||
returned: success
|
||||
type: str
|
||||
update_at:
|
||||
description: The date and time when the resource was updated
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
|
||||
class ComputeServiceInfoModule(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
binary=dict(required=False, default=None),
|
||||
host=dict(required=False, default=None),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
binary = self.params['binary']
|
||||
host = self.params['host']
|
||||
filters = {}
|
||||
if binary:
|
||||
filters['binary'] = binary
|
||||
if host:
|
||||
filters['host'] = host
|
||||
services = self.conn.compute.services(**filters)
|
||||
services = [service.to_dict(computed=True) for service in services]
|
||||
self.exit_json(changed=False, openstack_compute_services=services)
|
||||
|
||||
|
||||
def main():
|
||||
module = ComputeServiceInfoModule()
|
||||
module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -136,6 +136,9 @@ class DnsZoneInfoModule(OpenStackModule):
|
||||
description=dict(required=False, type='str'),
|
||||
ttl=dict(required=False, type='int')
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ short_description: Add/Remove floating IP from an instance
|
||||
description:
|
||||
- Add or Remove a floating IP to an instance.
|
||||
- Returns the floating IP when attaching only if I(wait=true).
|
||||
- When detaching a floating IP there might be a delay until an instance does not list the floating IP any more.
|
||||
options:
|
||||
server:
|
||||
description:
|
||||
@@ -24,9 +25,9 @@ options:
|
||||
type: str
|
||||
floating_ip_address:
|
||||
description:
|
||||
- A floating IP address to attach or to detach. Required only if I(state)
|
||||
is absent. When I(state) is present can be used to specify a IP address
|
||||
to attach.
|
||||
- A floating IP address to attach or to detach. When I(state) is present
|
||||
can be used to specify a IP address to attach. I(floating_ip_address)
|
||||
requires I(network) to be set.
|
||||
type: str
|
||||
reuse:
|
||||
description:
|
||||
@@ -49,7 +50,7 @@ options:
|
||||
wait:
|
||||
description:
|
||||
- When attaching a floating IP address, specify whether to wait for it to appear as attached.
|
||||
- Must be set to C(yes) for the module to return the value of the floating IP.
|
||||
- Must be set to C(yes) for the module to return the value of the floating IP when attaching.
|
||||
type: bool
|
||||
default: 'no'
|
||||
timeout:
|
||||
@@ -118,8 +119,8 @@ EXAMPLES = '''
|
||||
server: cattle001
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import remove_values
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
import itertools
|
||||
|
||||
|
||||
class NetworkingFloatingIPModule(OpenStackModule):
|
||||
@@ -137,16 +138,57 @@ class NetworkingFloatingIPModule(OpenStackModule):
|
||||
purge=dict(required=False, type='bool', default=False),
|
||||
)
|
||||
|
||||
module_kwargs = dict()
|
||||
module_kwargs = dict(
|
||||
required_if=[
|
||||
['state', 'absent', ['floating_ip_address']]
|
||||
],
|
||||
required_by=dict(
|
||||
floating_ip_address=('network',)
|
||||
)
|
||||
)
|
||||
|
||||
def _get_floating_ip(self, floating_ip_address):
|
||||
f_ips = self.conn.search_floating_ips(
|
||||
filters={'floating_ip_address': floating_ip_address})
|
||||
|
||||
if not f_ips:
|
||||
return None
|
||||
|
||||
return f_ips[0]
|
||||
|
||||
def _list_floating_ips(self, server):
|
||||
return itertools.chain.from_iterable([
|
||||
(addr['addr'] for addr in server.addresses[net] if addr['OS-EXT-IPS:type'] == 'floating')
|
||||
for net in server.addresses
|
||||
])
|
||||
|
||||
def _match_floating_ip(self, server,
|
||||
floating_ip_address,
|
||||
network_id,
|
||||
fixed_address,
|
||||
nat_destination):
|
||||
|
||||
if floating_ip_address:
|
||||
return self._get_floating_ip(floating_ip_address)
|
||||
elif not fixed_address and nat_destination:
|
||||
nat_destination_name = self.conn.get_network(nat_destination)['name']
|
||||
return next(
|
||||
(self._get_floating_ip(addr['addr'])
|
||||
for addr in server.addresses.get(nat_destination_name, [])
|
||||
if addr['OS-EXT-IPS:type'] == 'floating'),
|
||||
None)
|
||||
else:
|
||||
# not floating_ip_address and (fixed_address or not nat_destination)
|
||||
|
||||
# get any of the floating ips that matches fixed_address and/or network
|
||||
f_ip_addrs = self._list_floating_ips(server)
|
||||
f_ips = [f_ip for f_ip in self.conn.list_floating_ips() if f_ip['floating_ip_address'] in f_ip_addrs]
|
||||
return next(
|
||||
(f_ip for f_ip in f_ips
|
||||
if ((fixed_address and f_ip.fixed_ip_address == fixed_address) or not fixed_address)
|
||||
and ((network_id and f_ip.network == network_id) or not network_id)),
|
||||
None)
|
||||
|
||||
def run(self):
|
||||
server_name_or_id = self.params['server']
|
||||
state = self.params['state']
|
||||
@@ -160,83 +202,96 @@ class NetworkingFloatingIPModule(OpenStackModule):
|
||||
purge = self.params['purge']
|
||||
|
||||
server = self.conn.get_server(server_name_or_id)
|
||||
if server is None:
|
||||
if not server:
|
||||
self.fail_json(
|
||||
msg="server {0} not found".format(server_name_or_id))
|
||||
|
||||
# Extract floating ips from server
|
||||
f_ip_addrs = self._list_floating_ips(server)
|
||||
|
||||
# Get details about requested floating ip
|
||||
f_ip = self._get_floating_ip(floating_ip_address) if floating_ip_address else None
|
||||
|
||||
if network:
|
||||
network_id = self.conn.get_network(name_or_id=network)["id"]
|
||||
else:
|
||||
network_id = None
|
||||
|
||||
if state == 'present':
|
||||
# If f_ip already assigned to server, check that it matches
|
||||
# requirements.
|
||||
public_ip = self.conn.get_server_public_ip(server)
|
||||
f_ip = self._get_floating_ip(public_ip) if public_ip else public_ip
|
||||
if f_ip:
|
||||
if network:
|
||||
network_id = self.conn.get_network(name_or_id=network)["id"]
|
||||
else:
|
||||
network_id = None
|
||||
# check if we have floating ip on given nat_destination network
|
||||
if nat_destination:
|
||||
nat_floating_addrs = [
|
||||
addr for addr in server.addresses.get(
|
||||
self.conn.get_network(nat_destination)['name'], [])
|
||||
if addr['addr'] == public_ip
|
||||
and addr['OS-EXT-IPS:type'] == 'floating'
|
||||
]
|
||||
if floating_ip_address and f_ip and floating_ip_address in f_ip_addrs:
|
||||
# Floating ip address has been assigned to server
|
||||
self.exit_json(changed=False, floating_ip=f_ip)
|
||||
|
||||
if len(nat_floating_addrs) == 0:
|
||||
self.fail_json(
|
||||
msg="server {server} already has a "
|
||||
"floating-ip on a different "
|
||||
"nat-destination than '{nat_destination}'"
|
||||
.format(server=server_name_or_id,
|
||||
nat_destination=nat_destination))
|
||||
if f_ip and f_ip['attached'] and floating_ip_address not in f_ip_addrs:
|
||||
# Requested floating ip has been attached to different server
|
||||
self.fail_json(msg="floating-ip {floating_ip_address} already has been attached to different server"
|
||||
.format(floating_ip_address=floating_ip_address))
|
||||
|
||||
if all([fixed_address, f_ip.fixed_ip_address == fixed_address,
|
||||
network, f_ip.network != network_id]):
|
||||
# Current state definitely conflicts with requirements
|
||||
self.fail_json(
|
||||
msg="server {server} already has a "
|
||||
"floating-ip on requested "
|
||||
"interface but it doesn't match "
|
||||
"requested network {network}: {fip}"
|
||||
.format(server=server_name_or_id,
|
||||
network=network,
|
||||
fip=remove_values(f_ip, self.no_log_values)))
|
||||
if not network or f_ip.network == network_id:
|
||||
# Requirements are met
|
||||
self.exit_json(changed=False, floating_ip=f_ip)
|
||||
if not floating_ip_address:
|
||||
# No specific floating ip requested, i.e. if any floating ip is already assigned to server,
|
||||
# check that it matches requirements.
|
||||
|
||||
# Requirements are vague enough to ignore existing f_ip and try
|
||||
# to create a new f_ip to the server.
|
||||
if not fixed_address and nat_destination:
|
||||
# Check if we have any floating ip on the given nat_destination network
|
||||
nat_destination_name = self.conn.get_network(nat_destination)['name']
|
||||
for addr in server.addresses.get(nat_destination_name, []):
|
||||
if addr['OS-EXT-IPS:type'] == 'floating':
|
||||
# A floating ip address has been assigned to the requested nat_destination
|
||||
f_ip = self._get_floating_ip(addr['addr'])
|
||||
self.exit_json(changed=False, floating_ip=f_ip)
|
||||
# else fixed_address or not nat_destination, hence an
|
||||
# analysis of all floating ips of server is required
|
||||
f_ips = [f_ip for f_ip in self.conn.list_floating_ips() if f_ip['floating_ip_address'] in f_ip_addrs]
|
||||
for f_ip in f_ips:
|
||||
if network_id and f_ip.network != network_id:
|
||||
# requested network does not match network of floating ip
|
||||
continue
|
||||
|
||||
if not fixed_address and not nat_destination:
|
||||
# any floating ip will fullfil these requirements
|
||||
self.exit_json(changed=False, floating_ip=f_ip)
|
||||
|
||||
if fixed_address and f_ip.fixed_ip_address == fixed_address:
|
||||
# a floating ip address has been assigned that points to the requested fixed_address
|
||||
self.exit_json(changed=False, floating_ip=f_ip)
|
||||
|
||||
if floating_ip_address and not f_ip:
|
||||
# openstacksdk's create_ip requires floating_ip_address and floating_network_id to be set
|
||||
self.conn.network.create_ip(floating_ip_address=floating_ip_address, floating_network_id=network_id)
|
||||
# Else floating ip either does not exist or has not been attached yet
|
||||
|
||||
# Both floating_ip_address and network are mutually exclusive in add_ips_to_server, i.e.
|
||||
# add_ips_to_server will ignore floating_ip_address if network is set
|
||||
# Ref.: https://github.com/openstack/openstacksdk/blob/a6b0ece2821ea79330c4067100295f6bdcbe456e/openstack/cloud/_floating_ip.py#L987
|
||||
server = self.conn.add_ips_to_server(
|
||||
server=server, ips=floating_ip_address, ip_pool=network,
|
||||
reuse=reuse, fixed_address=fixed_address, wait=wait,
|
||||
server=server,
|
||||
ips=floating_ip_address,
|
||||
ip_pool=network if not floating_ip_address else None,
|
||||
reuse=reuse,
|
||||
fixed_address=fixed_address,
|
||||
wait=wait,
|
||||
timeout=timeout, nat_destination=nat_destination)
|
||||
fip_address = self.conn.get_server_public_ip(server)
|
||||
# Update the floating IP status
|
||||
f_ip = self._get_floating_ip(fip_address)
|
||||
|
||||
# Update the floating ip status
|
||||
f_ip = self._match_floating_ip(server, floating_ip_address, network_id, fixed_address, nat_destination)
|
||||
self.exit_json(changed=True, floating_ip=f_ip)
|
||||
|
||||
elif state == 'absent':
|
||||
if floating_ip_address is None:
|
||||
if not server_name_or_id:
|
||||
self.fail_json(msg="either server or floating_ip_address are required")
|
||||
server = self.conn.get_server(server_name_or_id)
|
||||
floating_ip_address = self.conn.get_server_public_ip(server)
|
||||
|
||||
f_ip = self._get_floating_ip(floating_ip_address)
|
||||
|
||||
f_ip = self._match_floating_ip(server, floating_ip_address, network_id, fixed_address, nat_destination)
|
||||
if not f_ip:
|
||||
# Nothing to detach
|
||||
self.exit_json(changed=False)
|
||||
changed = False
|
||||
|
||||
if f_ip["fixed_ip_address"]:
|
||||
self.conn.detach_ip_from_server(
|
||||
server_id=server['id'], floating_ip_id=f_ip['id'])
|
||||
self.conn.detach_ip_from_server(server_id=server['id'], floating_ip_id=f_ip['id'])
|
||||
# OpenStackSDK sets {"port_id": None} to detach a floating ip from an instance,
|
||||
# but there might be a delay until a server does not list it in addresses any more.
|
||||
|
||||
# Update the floating IP status
|
||||
f_ip = self.conn.get_floating_ip(id=f_ip['id'])
|
||||
changed = True
|
||||
|
||||
if purge:
|
||||
self.conn.delete_floating_ip(f_ip['id'])
|
||||
self.exit_json(changed=True)
|
||||
|
||||
@@ -150,6 +150,9 @@ class FloatingIPInfoModule(OpenStackModule):
|
||||
router=dict(required=False),
|
||||
status=dict(required=False, choices=['active', 'down']),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -162,7 +165,6 @@ class FloatingIPInfoModule(OpenStackModule):
|
||||
router = self.params['router']
|
||||
status = self.params['status']
|
||||
|
||||
data = []
|
||||
query = {}
|
||||
if description:
|
||||
query['description'] = description
|
||||
@@ -191,15 +193,8 @@ class FloatingIPInfoModule(OpenStackModule):
|
||||
if status:
|
||||
query['status'] = status.upper()
|
||||
|
||||
for raw in self.conn.network.ips(**query):
|
||||
dt = raw.to_dict()
|
||||
dt.pop('location')
|
||||
data.append(dt)
|
||||
|
||||
self.exit_json(
|
||||
changed=False,
|
||||
floating_ips=data
|
||||
)
|
||||
ips = [ip.to_dict(computed=False) for ip in self.conn.network.ips(**query)]
|
||||
self.exit_json(changed=False, floating_ips=ips)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -135,14 +135,14 @@ class ComputeHostAggregateModule(OpenStackModule):
|
||||
if hosts is None:
|
||||
return
|
||||
|
||||
hosts_to_add = set(hosts) - set(aggregate.get("hosts", []))
|
||||
hosts_to_add = set(hosts) - set(aggregate.get("hosts") or [])
|
||||
for i in hosts_to_add:
|
||||
self.conn.add_host_to_aggregate(aggregate.id, i)
|
||||
|
||||
if not purge_hosts:
|
||||
return
|
||||
|
||||
hosts_to_remove = set(aggregate.get("hosts", [])) - set(hosts)
|
||||
hosts_to_remove = set(aggregate.get("hosts") or []) - set(hosts)
|
||||
for i in hosts_to_remove:
|
||||
self.conn.remove_host_from_aggregate(aggregate.id, i)
|
||||
|
||||
|
||||
@@ -104,7 +104,8 @@ class IdentityDomainModule(OpenStackModule):
|
||||
if self.params['description'] is not None and \
|
||||
domain.description != self.params['description']:
|
||||
return True
|
||||
if domain.enabled != self.params['enabled']:
|
||||
if domain.get(
|
||||
"is_enabled", domain.get("enabled")) != self.params['enabled']:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -126,7 +127,7 @@ class IdentityDomainModule(OpenStackModule):
|
||||
enabled = self.params['enabled']
|
||||
state = self.params['state']
|
||||
|
||||
domains = self.conn.search_domains(filters=dict(name=name))
|
||||
domains = list(self.conn.identity.domains(name=name))
|
||||
|
||||
if len(domains) > 1:
|
||||
self.fail_json(msg='Domain name %s is not unique' % name)
|
||||
@@ -151,7 +152,10 @@ class IdentityDomainModule(OpenStackModule):
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
self.exit_json(changed=changed, domain=domain, id=domain.id)
|
||||
if hasattr(domain, "to_dict"):
|
||||
domain = domain.to_dict()
|
||||
domain.pop("location")
|
||||
self.exit_json(changed=changed, domain=domain, id=domain['id'])
|
||||
|
||||
elif state == 'absent':
|
||||
if domain is None:
|
||||
|
||||
@@ -92,7 +92,8 @@ class IdentityDomainInfoModule(OpenStackModule):
|
||||
module_kwargs = dict(
|
||||
mutually_exclusive=[
|
||||
['name', 'filters'],
|
||||
]
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
deprecated_names = ('openstack.cloud.identity_domain_facts')
|
||||
|
||||
@@ -119,6 +119,9 @@ class IdentityGroupInfoModule(OpenStackModule):
|
||||
domain=dict(required=False, default=None),
|
||||
filters=dict(required=False, type='dict', default=None),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
name = self.params['name']
|
||||
|
||||
@@ -116,6 +116,9 @@ class IdentityUserInfoModule(OpenStackModule):
|
||||
domain=dict(required=False, default=None),
|
||||
filters=dict(required=False, type='dict', default=None),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
deprecated_names = ('openstack.cloud.identity_user_facts')
|
||||
|
||||
@@ -138,12 +141,7 @@ class IdentityUserInfoModule(OpenStackModule):
|
||||
else:
|
||||
self.fail_json(msg='Domain name or ID does not exist')
|
||||
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
filters['domain_id'] = domain
|
||||
|
||||
users = self.conn.search_users(name, filters)
|
||||
users = self.conn.search_users(name, filters, domain_id=domain)
|
||||
self.exit_json(changed=False, openstack_users=users)
|
||||
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ options:
|
||||
description:
|
||||
- Prevent image from being deleted
|
||||
type: bool
|
||||
default: 'no'
|
||||
default: false
|
||||
filename:
|
||||
description:
|
||||
- The path to the file which has to be uploaded
|
||||
|
||||
@@ -88,10 +88,6 @@ openstack_image:
|
||||
description: Image created at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
deleted:
|
||||
description: Image deleted flag.
|
||||
returned: success
|
||||
type: bool
|
||||
container_format:
|
||||
description: Container format of the image.
|
||||
returned: success
|
||||
@@ -132,10 +128,6 @@ openstack_image:
|
||||
description: Is public flag of the image.
|
||||
returned: success
|
||||
type: bool
|
||||
deleted_at:
|
||||
description: Image deleted at timestamp.
|
||||
returned: success
|
||||
type: str
|
||||
size:
|
||||
description: Size of the image.
|
||||
returned: success
|
||||
@@ -157,6 +149,9 @@ class ImageInfoModule(OpenStackModule):
|
||||
image=dict(type='str', required=False),
|
||||
properties=dict(type='dict', required=False),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
@@ -115,6 +115,9 @@ class KeyPairInfoModule(OpenStackModule):
|
||||
limit=dict(type='int', required=False),
|
||||
marker=dict(type='str', required=False)
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
name = self.params['name']
|
||||
|
||||
@@ -45,7 +45,7 @@ options:
|
||||
- The number of successful checks before changing the operating status of the member to ONLINE.
|
||||
max_retries_down:
|
||||
type: 'str'
|
||||
default: 3
|
||||
default: '3'
|
||||
description:
|
||||
- The number of allowed check failures before changing the operating status of the member to ERROR. A valid value is from 1 to 10. The default is 3.
|
||||
resp_timeout:
|
||||
@@ -60,7 +60,7 @@ options:
|
||||
type: bool
|
||||
expected_codes:
|
||||
type: 'str'
|
||||
default: 200
|
||||
default: '200'
|
||||
description:
|
||||
- The list of HTTP status codes expected in response from the member to declare it healthy. Specify one of the following values
|
||||
A single value, such as 200
|
||||
@@ -223,10 +223,10 @@ class HealthMonitorModule(OpenStackModule):
|
||||
expected_codes=dict(required=False, default="200"),
|
||||
admin_state_up=dict(required=False, default=True, type='bool'),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
http_method=dict(default="GET", requried=False, choices=["GET", "CONNECT", "DELETE",
|
||||
http_method=dict(default="GET", required=False, choices=["GET", "CONNECT", "DELETE",
|
||||
"HEAD", "OPTIONS", "PATCH",
|
||||
"POST", "PUT", "TRACE"]),
|
||||
url_path=dict(default="/", requires=False),
|
||||
url_path=dict(default="/", required=False),
|
||||
type=dict(default='HTTP',
|
||||
choices=['HTTP', 'HTTPS', 'PING', 'SCTP', 'TCP', 'TLS-HELLO', 'UDP-CONNECT']))
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ options:
|
||||
protocol:
|
||||
description:
|
||||
- The protocol for the listener.
|
||||
choices: [HTTP, HTTPS, TCP, TERMINATED_HTTPS]
|
||||
choices: [HTTP, HTTPS, TCP, TERMINATED_HTTPS, UDP, SCTP]
|
||||
default: HTTP
|
||||
type: str
|
||||
protocol_port:
|
||||
@@ -38,6 +38,16 @@ options:
|
||||
- The protocol port number for the listener.
|
||||
default: 80
|
||||
type: int
|
||||
timeout_client_data:
|
||||
description:
|
||||
- Client inactivity timeout in milliseconds.
|
||||
default: 50000
|
||||
type: int
|
||||
timeout_member_data:
|
||||
description:
|
||||
- Member inactivity timeout in milliseconds.
|
||||
default: 50000
|
||||
type: int
|
||||
wait:
|
||||
description:
|
||||
- If the module should wait for the load balancer to be ACTIVE.
|
||||
@@ -108,6 +118,14 @@ listener:
|
||||
description: The protocol port number for the listener.
|
||||
type: int
|
||||
sample: 80
|
||||
timeout_client_data:
|
||||
description: Client inactivity timeout in milliseconds.
|
||||
type: int
|
||||
sample: 50000
|
||||
timeout_member_data:
|
||||
description: Member inactivity timeout in milliseconds.
|
||||
type: int
|
||||
sample: 50000
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
@@ -139,6 +157,18 @@ EXAMPLES = '''
|
||||
state: absent
|
||||
name: test-listener
|
||||
loadbalancer: test-loadbalancer
|
||||
|
||||
# Create a listener, increase timeouts for connection persistence (for SSH for example).
|
||||
- openstack.cloud.lb_listener:
|
||||
cloud: mycloud
|
||||
endpoint_type: admin
|
||||
state: present
|
||||
name: test-listener
|
||||
loadbalancer: test-loadbalancer
|
||||
protocol: TCP
|
||||
protocol_port: 22
|
||||
timeout_client_data: 1800000
|
||||
timeout_member_data: 1800000
|
||||
'''
|
||||
|
||||
import time
|
||||
@@ -152,8 +182,10 @@ class LoadbalancerListenerModule(OpenStackModule):
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
loadbalancer=dict(required=True),
|
||||
protocol=dict(default='HTTP',
|
||||
choices=['HTTP', 'HTTPS', 'TCP', 'TERMINATED_HTTPS']),
|
||||
choices=['HTTP', 'HTTPS', 'TCP', 'TERMINATED_HTTPS', 'UDP', 'SCTP']),
|
||||
protocol_port=dict(default=80, type='int', required=False),
|
||||
timeout_client_data=dict(default=50000, type='int', required=False),
|
||||
timeout_member_data=dict(default=50000, type='int', required=False),
|
||||
)
|
||||
module_kwargs = dict()
|
||||
|
||||
@@ -205,6 +237,8 @@ class LoadbalancerListenerModule(OpenStackModule):
|
||||
loadbalancer_id=loadbalancer_id,
|
||||
protocol=self.params['protocol'],
|
||||
protocol_port=self.params['protocol_port'],
|
||||
timeout_client_data=self.params['timeout_client_data'],
|
||||
timeout_member_data=self.params['timeout_member_data'],
|
||||
)
|
||||
changed = True
|
||||
|
||||
|
||||
@@ -51,6 +51,14 @@ options:
|
||||
into ACTIVE state.
|
||||
default: 180
|
||||
type: int
|
||||
monitor_address:
|
||||
description:
|
||||
- IP address used to monitor this member
|
||||
type: str
|
||||
monitor_port:
|
||||
description:
|
||||
- Port used to monitor this member
|
||||
type: int
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
@@ -141,6 +149,8 @@ class LoadbalancerMemberModule(OpenStackModule):
|
||||
address=dict(default=None),
|
||||
protocol_port=dict(default=80, type='int'),
|
||||
subnet_id=dict(default=None),
|
||||
monitor_address=dict(default=None),
|
||||
monitor_port=dict(default=None, type='int')
|
||||
)
|
||||
module_kwargs = dict()
|
||||
|
||||
@@ -191,7 +201,9 @@ class LoadbalancerMemberModule(OpenStackModule):
|
||||
address=self.params['address'],
|
||||
name=name,
|
||||
protocol_port=self.params['protocol_port'],
|
||||
subnet_id=self.params['subnet_id']
|
||||
subnet_id=self.params['subnet_id'],
|
||||
monitor_address=self.params['monitor_address'],
|
||||
monitor_port=self.params['monitor_port']
|
||||
)
|
||||
changed = True
|
||||
|
||||
|
||||
@@ -296,6 +296,35 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
|
||||
|
||||
class LoadBalancerModule(OpenStackModule):
|
||||
|
||||
def _wait_for_pool(self, pool, provisioning_status, operating_status, failures, interval=5):
|
||||
"""Wait for pool to be in a particular provisioning and operating status."""
|
||||
timeout = self.params['timeout'] # reuse loadbalancer timeout
|
||||
|
||||
total_sleep = 0
|
||||
if failures is None:
|
||||
failures = []
|
||||
|
||||
while total_sleep < timeout:
|
||||
pool = self.conn.load_balancer.find_pool(name_or_id=pool.id)
|
||||
if pool:
|
||||
if pool.provisioning_status == provisioning_status and pool.operating_status == operating_status:
|
||||
return None
|
||||
if pool.provisioning_status in failures:
|
||||
self.fail_json(
|
||||
msg="Pool %s transitioned to failure state %s" %
|
||||
(pool.id, pool.provisioning_status)
|
||||
)
|
||||
else:
|
||||
if provisioning_status == "DELETED":
|
||||
return None
|
||||
else:
|
||||
self.fail_json(
|
||||
msg="Pool %s transitioned to DELETED" % pool.id
|
||||
)
|
||||
|
||||
time.sleep(interval)
|
||||
total_sleep += interval
|
||||
|
||||
def _wait_for_lb(self, lb, status, failures, interval=5):
|
||||
"""Wait for load balancer to be in a particular provisioning status."""
|
||||
timeout = self.params['timeout']
|
||||
@@ -497,6 +526,7 @@ class LoadBalancerModule(OpenStackModule):
|
||||
protocol=protocol,
|
||||
lb_algorithm=lb_algorithm
|
||||
)
|
||||
self._wait_for_pool(pool, "ACTIVE", "ONLINE", ["ERROR"])
|
||||
changed = True
|
||||
|
||||
# Ensure members in the pool
|
||||
@@ -538,6 +568,7 @@ class LoadBalancerModule(OpenStackModule):
|
||||
protocol_port=protocol_port,
|
||||
subnet_id=subnet_id
|
||||
)
|
||||
self._wait_for_pool(pool, "ACTIVE", "ONLINE", ["ERROR"])
|
||||
changed = True
|
||||
|
||||
# Associate public ip to the load balancer VIP. If
|
||||
|
||||
@@ -124,6 +124,9 @@ class NetworkInfoModule(OpenStackModule):
|
||||
name=dict(required=False, default=None),
|
||||
filters=dict(required=False, type='dict', default=None)
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
237
plugins/modules/neutron_rbac_policies_info.py
Normal file
237
plugins/modules/neutron_rbac_policies_info.py
Normal file
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/python
|
||||
# coding: utf-8 -*-
|
||||
|
||||
# Copyright: Ansible Project
|
||||
# (c) 2021, Ashraf Hasson <ahasson@redhat.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: neutron_rbac_policies_info
|
||||
short_description: Fetch Neutron policies.
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Get RBAC policies against a network, security group or a QoS Policy for one or more projects.
|
||||
- If a C(policy_id) was not provided, this module will attempt to fetch all available policies.
|
||||
- Accepts same arguments as OpenStackSDK network proxy C(find_rbac_policy) and C(rbac_policies) functions which are ultimately passed over to C(RBACPolicy)
|
||||
- All parameters passed in to this module act as a filter for when no C(policy_id) was provided, otherwise they're ignored.
|
||||
- Returns None if no matching policy was found as opposed to failing.
|
||||
|
||||
options:
|
||||
policy_id:
|
||||
description:
|
||||
- The RBAC policy ID
|
||||
- If provided, all other filters are ignored
|
||||
type: str
|
||||
object_id:
|
||||
description:
|
||||
- The object ID (the subject of the policy) to which the RBAC rules applies
|
||||
- This would be the ID of a network, security group or a qos policy
|
||||
- Mutually exclusive with the C(object_type)
|
||||
type: str
|
||||
object_type:
|
||||
description:
|
||||
- Can be one of the following object types C(network), C(security_group) or C(qos_policy)
|
||||
- Mutually exclusive with the C(object_id)
|
||||
choices: ['network', 'security_group', 'qos_policy']
|
||||
type: str
|
||||
target_project_id:
|
||||
description:
|
||||
- Filters the RBAC rules based on the target project id
|
||||
- Logically AND'ed with other filters
|
||||
- Mutually exclusive with C(project_id)
|
||||
type: str
|
||||
project_id:
|
||||
description:
|
||||
- Filters the RBAC rules based on the project id to which the object belongs to
|
||||
- Logically AND'ed with other filters
|
||||
- Mutually exclusive with C(target_project_id)
|
||||
type: str
|
||||
project:
|
||||
description:
|
||||
- Filters the RBAC rules based on the project name
|
||||
- Logically AND'ed with other filters
|
||||
type: str
|
||||
action:
|
||||
description:
|
||||
- Can be either of the following options C(access_as_shared) | C(access_as_external)
|
||||
- Logically AND'ed with other filters
|
||||
choices: ['access_as_shared', 'access_as_external']
|
||||
type: str
|
||||
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Gather all rbac policies for a project
|
||||
- name: Get all rbac policies for {{ project }}
|
||||
openstack.cloud.neutron_rbac_policies_info:
|
||||
project_id: "{{ project.id }}"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
# return value can either be plural or signular depending on what was passed in as parameters
|
||||
policies:
|
||||
description:
|
||||
- List of rbac policies, this could also be returned as a singular element, i.e., 'policy'
|
||||
type: complex
|
||||
returned: always
|
||||
contains:
|
||||
object_id:
|
||||
description:
|
||||
- The UUID of the object to which the RBAC rules apply
|
||||
type: str
|
||||
sample: "7422172b-2961-475c-ac68-bd0f2a9960ad"
|
||||
target_project_id:
|
||||
description:
|
||||
- The UUID of the target project
|
||||
type: str
|
||||
sample: "c201a689c016435c8037977166f77368"
|
||||
project_id:
|
||||
description:
|
||||
- The UUID of the project to which access is granted
|
||||
type: str
|
||||
sample: "84b8774d595b41e89f3dfaa1fd76932c"
|
||||
object_type:
|
||||
description:
|
||||
- The object type to which the RBACs apply
|
||||
type: str
|
||||
sample: "network"
|
||||
action:
|
||||
description:
|
||||
- The access model specified by the RBAC rules
|
||||
type: str
|
||||
sample: "access_as_shared"
|
||||
id:
|
||||
description:
|
||||
- The ID of the RBAC rule/policy
|
||||
type: str
|
||||
sample: "4154ce0c-71a7-4d87-a905-09762098ddb9"
|
||||
name:
|
||||
description:
|
||||
- The name of the RBAC rule; usually null
|
||||
type: str
|
||||
sample: null
|
||||
location:
|
||||
description:
|
||||
- A dictionary of the project details to which access is granted
|
||||
type: dict
|
||||
sample: >-
|
||||
{
|
||||
"cloud": "devstack",
|
||||
"region_name": "",
|
||||
"zone": null,
|
||||
"project": {
|
||||
"id": "84b8774d595b41e89f3dfaa1fd76932c",
|
||||
"name": null,
|
||||
"domain_id": null,
|
||||
"domain_name": null
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
import re
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
|
||||
class NeutronRbacPoliciesInfo(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
policy_id=dict(),
|
||||
object_id=dict(), # ID of the object that this RBAC policy affects.
|
||||
object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects.
|
||||
target_project_id=dict(), # The ID of the project this RBAC will be enforced.
|
||||
project_id=dict(), # The owner project ID.
|
||||
project=dict(),
|
||||
action=dict(choices=['access_as_external', 'access_as_shared']), # Action for the RBAC policy.
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
def _filter_policies_by(self, policies, key, value):
|
||||
filtered = []
|
||||
regexp = re.compile(r"location\.project\.([A-Za-z]+)")
|
||||
if regexp.match(key):
|
||||
attribute = key.split('.')[-1]
|
||||
for p in policies:
|
||||
if p['location']['project'][attribute] == value:
|
||||
filtered.append(p)
|
||||
else:
|
||||
for p in policies:
|
||||
if getattr(p, key) == value:
|
||||
filtered.append(p)
|
||||
|
||||
return filtered
|
||||
|
||||
def _get_rbac_policies(self):
|
||||
object_type = self.params.get('object_type')
|
||||
project_id = self.params.get('project_id')
|
||||
action = self.params.get('action')
|
||||
|
||||
search_attributes = {}
|
||||
if object_type is not None:
|
||||
search_attributes['object_type'] = object_type
|
||||
if project_id is not None:
|
||||
search_attributes['project_id'] = project_id
|
||||
if action is not None:
|
||||
search_attributes['action'] = action
|
||||
|
||||
try:
|
||||
policies = []
|
||||
generator = self.conn.network.rbac_policies(**search_attributes)
|
||||
for p in generator:
|
||||
policies.append(p)
|
||||
except self.sdk.exceptions.OpenStackCloudException as ex:
|
||||
self.fail_json(msg='Failed to get RBAC policies: {0}'.format(str(ex)))
|
||||
|
||||
return policies
|
||||
|
||||
def run(self):
|
||||
policy_id = self.params.get('policy_id')
|
||||
object_id = self.params.get('object_id')
|
||||
object_type = self.params.get('object_type')
|
||||
project_id = self.params.get('project_id')
|
||||
project = self.params.get('project')
|
||||
target_project_id = self.params.get('target_project_id')
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=False)
|
||||
|
||||
if policy_id is not None:
|
||||
try:
|
||||
policy = self.conn.network.get_rbac_policy(policy_id)
|
||||
self.exit_json(changed=False, policy=policy)
|
||||
except self.sdk.exceptions.ResourceNotFound:
|
||||
self.exit_json(changed=False, policy=None)
|
||||
except self.sdk.exceptions.OpenStackCloudException as ex:
|
||||
self.fail_json(msg='Failed to get RBAC policy: {0}'.format(str(ex)))
|
||||
else:
|
||||
if object_id is not None and object_type is not None:
|
||||
self.fail_json(msg='object_id and object_type are mutually exclusive, please specify one of the two.')
|
||||
if project_id is not None and target_project_id is not None:
|
||||
self.fail_json(msg='project_id and target_project_id are mutually exclusive, please specify one of the two.')
|
||||
|
||||
filtered_policies = self._get_rbac_policies()
|
||||
|
||||
if project is not None:
|
||||
filtered_policies = self._filter_policies_by(filtered_policies, 'location.project.name', project)
|
||||
if object_id is not None:
|
||||
filtered_policies = self._filter_policies_by(filtered_policies, 'object_id', object_id)
|
||||
if target_project_id is not None:
|
||||
filtered_policies = self._filter_policies_by(filtered_policies, 'target_project_id', target_project_id)
|
||||
|
||||
self.exit_json(policies=filtered_policies, changed=False)
|
||||
|
||||
|
||||
def main():
|
||||
module = NeutronRbacPoliciesInfo()
|
||||
module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
308
plugins/modules/neutron_rbac_policy.py
Normal file
308
plugins/modules/neutron_rbac_policy.py
Normal file
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright: Ansible Project
|
||||
# (c) 2021, Ashraf Hasson <ahasson@redhat.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: neutron_rbac_policy
|
||||
short_description: Create or delete a Neutron policy to apply a RBAC rule against an object.
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Create a policy to apply a RBAC rule against a network, security group or a QoS Policy or update/delete an existing policy.
|
||||
- If a C(policy_id) was provided but not found, this module will attempt to create a new policy rather than error out when updating an existing rule.
|
||||
- Accepts same arguments as OpenStackSDK network proxy C(find_rbac_policy) and C(rbac_policies) functions which are ultimately passed over to C(RBACPolicy)
|
||||
|
||||
options:
|
||||
policy_id:
|
||||
description:
|
||||
- The RBAC policy ID
|
||||
- Required when deleting or updating an existing RBAC policy rule, ignored otherwise
|
||||
type: str
|
||||
object_id:
|
||||
description:
|
||||
- The object ID (the subject of the policy) to which the RBAC rule applies
|
||||
- Cannot be changed when updating an existing policy
|
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy
|
||||
type: str
|
||||
object_type:
|
||||
description:
|
||||
- Can be one of the following object types C(network), C(security_group) or C(qos_policy)
|
||||
- Cannot be changed when updating an existing policy
|
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy
|
||||
choices: ['network', 'security_group', 'qos_policy']
|
||||
type: str
|
||||
target_project_id:
|
||||
description:
|
||||
- The project to which access to be allowed or revoked/disallowed
|
||||
- Can be specified/changed when updating an existing policy
|
||||
- Required when creating or updating a RBAC policy rule, ignored when deleting a policy
|
||||
type: str
|
||||
project_id:
|
||||
description:
|
||||
- The project to which the object_id belongs
|
||||
- Cannot be changed when updating an existing policy
|
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy
|
||||
type: str
|
||||
action:
|
||||
description:
|
||||
- Can be either of the following options C(access_as_shared) | C(access_as_external)
|
||||
- Cannot be changed when updating an existing policy
|
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy
|
||||
choices: ['access_as_shared', 'access_as_external']
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether the RBAC rule should be C(present) or C(absent).
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Ensure network RBAC policy exists
|
||||
- name: Create a new network RBAC policy
|
||||
neutron_rbac_policy:
|
||||
object_id: '7422172b-2961-475c-ac68-bd0f2a9960ad'
|
||||
object_type: 'network'
|
||||
target_project_id: 'a12f9ce1de0645e0a0b01c2e679f69ec'
|
||||
project_id: '84b8774d595b41e89f3dfaa1fd76932d'
|
||||
|
||||
# Update network RBAC policy
|
||||
- name: Update an existing network RBAC policy
|
||||
neutron_rbac_policy:
|
||||
policy_id: 'f625242a-6a73-47ac-8d1f-91440b2c617f'
|
||||
target_project_id: '163c89e065a94e069064e551e15daf0e'
|
||||
|
||||
# Delete an existing RBAC policy
|
||||
- name: Delete RBAC policy
|
||||
openstack.cloud.openstack.neutron_rbac_policy:
|
||||
policy_id: 'f625242a-6a73-47ac-8d1f-91440b2c617f'
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
policy:
|
||||
description:
|
||||
- A hash representing the policy
|
||||
type: complex
|
||||
returned: always
|
||||
contains:
|
||||
object_id:
|
||||
description:
|
||||
- The UUID of the object to which the RBAC rules apply
|
||||
type: str
|
||||
sample: "7422172b-2961-475c-ac68-bd0f2a9960ad"
|
||||
target_project_id:
|
||||
description:
|
||||
- The UUID of the target project
|
||||
type: str
|
||||
sample: "c201a689c016435c8037977166f77368"
|
||||
project_id:
|
||||
description:
|
||||
- The UUID of the project to which access is granted
|
||||
type: str
|
||||
sample: "84b8774d595b41e89f3dfaa1fd76932c"
|
||||
object_type:
|
||||
description:
|
||||
- The object type to which the RBACs apply
|
||||
type: str
|
||||
sample: "network"
|
||||
action:
|
||||
description:
|
||||
- The access model specified by the RBAC rules
|
||||
type: str
|
||||
sample: "access_as_shared"
|
||||
id:
|
||||
description:
|
||||
- The ID of the RBAC rule/policy
|
||||
type: str
|
||||
sample: "4154ce0c-71a7-4d87-a905-09762098ddb9"
|
||||
name:
|
||||
description:
|
||||
- The name of the RBAC rule; usually null
|
||||
type: str
|
||||
sample: null
|
||||
location:
|
||||
description:
|
||||
- A dictionary of the project details to which access is granted
|
||||
type: dict
|
||||
sample: >-
|
||||
{
|
||||
"cloud": "devstack",
|
||||
"region_name": "",
|
||||
"zone": null,
|
||||
"project": {
|
||||
"id": "84b8774d595b41e89f3dfaa1fd76932c",
|
||||
"name": null,
|
||||
"domain_id": null,
|
||||
"domain_name": null
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
|
||||
class NeutronRbacPolicy(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
policy_id=dict(),
|
||||
object_id=dict(), # ID of the object that this RBAC policy affects.
|
||||
object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects.
|
||||
target_project_id=dict(), # The ID of the project this RBAC will be enforced.
|
||||
project_id=dict(), # The owner project ID.
|
||||
action=dict(choices=['access_as_external', 'access_as_shared']), # Action for the RBAC policy.
|
||||
state=dict(default='present', choices=['absent', 'present'])
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
def _delete_rbac_policy(self, policy):
|
||||
"""
|
||||
Delete an existing RBAC policy
|
||||
returns: the "Changed" state
|
||||
"""
|
||||
|
||||
if policy is None:
|
||||
self.fail_json(msg='Must specify policy_id for delete')
|
||||
|
||||
try:
|
||||
self.conn.network.delete_rbac_policy(policy.id)
|
||||
except self.sdk.exceptions.OpenStackCloudException as ex:
|
||||
self.fail_json(msg='Failed to delete RBAC policy: {0}'.format(str(ex)))
|
||||
|
||||
return True
|
||||
|
||||
def _create_rbac_policy(self):
|
||||
"""
|
||||
Creates a new RBAC policy
|
||||
returns: the "Changed" state of the RBAC policy
|
||||
"""
|
||||
|
||||
object_id = self.params.get('object_id')
|
||||
object_type = self.params.get('object_type')
|
||||
target_project_id = self.params.get('target_project_id')
|
||||
project_id = self.params.get('project_id')
|
||||
action = self.params.get('action')
|
||||
|
||||
attributes = {
|
||||
'object_id': object_id,
|
||||
'object_type': object_type,
|
||||
'target_project_id': target_project_id,
|
||||
'project_id': project_id,
|
||||
'action': action
|
||||
}
|
||||
|
||||
if not all(attributes.values()):
|
||||
self.fail_json(msg='Missing one or more required parameter for creating a RBAC policy')
|
||||
|
||||
try:
|
||||
search_attributes = dict(attributes)
|
||||
del search_attributes['object_id']
|
||||
del search_attributes['target_project_id']
|
||||
policies = self.conn.network.rbac_policies(**search_attributes)
|
||||
for p in policies:
|
||||
if p.object_id == object_id and p.target_project_id == target_project_id:
|
||||
return (False, p)
|
||||
|
||||
# if no matching policy exists, attempt to create one
|
||||
policy = self.conn.network.create_rbac_policy(**attributes)
|
||||
except self.sdk.exceptions.OpenStackCloudException as ex:
|
||||
self.fail_json(msg='Failed to create RBAC policy: {0}'.format(str(ex)))
|
||||
|
||||
return (True, policy)
|
||||
|
||||
def _update_rbac_policy(self, policy):
|
||||
"""
|
||||
Updates an existing RBAC policy
|
||||
returns: the "Changed" state of the RBAC policy
|
||||
"""
|
||||
|
||||
object_id = self.params.get('object_id')
|
||||
object_type = self.params.get('object_type')
|
||||
target_project_id = self.params.get('target_project_id')
|
||||
project_id = self.params.get('project_id')
|
||||
action = self.params.get('action')
|
||||
|
||||
allowed_attributes = {
|
||||
'rbac_policy': policy.id,
|
||||
'target_project_id': target_project_id
|
||||
}
|
||||
|
||||
disallowed_attributes = {
|
||||
'object_id': object_id,
|
||||
'object_type': object_type,
|
||||
'project_id': project_id,
|
||||
'action': action
|
||||
}
|
||||
|
||||
if not all(allowed_attributes.values()):
|
||||
self.fail_json(msg='Missing one or more required parameter for updating a RBAC policy')
|
||||
|
||||
if any(disallowed_attributes.values()):
|
||||
self.fail_json(msg='Cannot change disallowed parameters while updating a RBAC policy: ["object_id", "object_type", "project_id", "action"]')
|
||||
|
||||
try:
|
||||
policy = self.conn.network.update_rbac_policy(**allowed_attributes)
|
||||
except self.sdk.exceptions.OpenStackCloudException as ex:
|
||||
self.fail_json(msg='Failed to update the RBAC policy: {0}'.format(str(ex)))
|
||||
|
||||
return (True, policy)
|
||||
|
||||
def _policy_state_change(self, policy):
|
||||
state = self.params['state']
|
||||
if state == 'present':
|
||||
if not policy:
|
||||
return True
|
||||
if state == 'absent' and policy:
|
||||
return True
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
policy_id = self.params.get('policy_id')
|
||||
state = self.params.get('state')
|
||||
|
||||
if policy_id is not None:
|
||||
try:
|
||||
policy = self.conn.network.get_rbac_policy(policy_id)
|
||||
except self.sdk.exceptions.ResourceNotFound:
|
||||
policy = None
|
||||
except self.sdk.exceptions.OpenStackCloudException as ex:
|
||||
self.fail_json(msg='Failed to get RBAC policy: {0}'.format(str(ex)))
|
||||
else:
|
||||
policy = None
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._policy_state_change(policy), policy=policy)
|
||||
|
||||
if state == 'absent':
|
||||
if policy is None and policy_id:
|
||||
self.exit_json(changed=False)
|
||||
if policy_id is None:
|
||||
self.fail_json(msg='Must specify policy_id when state is absent')
|
||||
if policy is not None:
|
||||
changed = self._delete_rbac_policy(policy)
|
||||
self.exit_json(changed=changed)
|
||||
# state == 'present'
|
||||
else:
|
||||
if policy is None:
|
||||
(changed, new_policy) = self._create_rbac_policy()
|
||||
else:
|
||||
(changed, new_policy) = self._update_rbac_policy(policy)
|
||||
|
||||
self.exit_json(changed=changed, policy=new_policy)
|
||||
|
||||
|
||||
def main():
|
||||
module = NeutronRbacPolicy()
|
||||
module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -123,6 +123,14 @@ options:
|
||||
description:
|
||||
- Binding profile dict that the port should be created with.
|
||||
type: dict
|
||||
dns_name:
|
||||
description:
|
||||
- The dns name of the port ( only with dns-integration enabled )
|
||||
type: str
|
||||
dns_domain:
|
||||
description:
|
||||
- The dns domain of the port ( only with dns-integration enabled )
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
@@ -302,7 +310,9 @@ class NetworkPortModule(OpenStackModule):
|
||||
choices=['normal', 'direct', 'direct-physical',
|
||||
'macvtap', 'baremetal', 'virtio-forwarder']),
|
||||
port_security_enabled=dict(default=None, type='bool'),
|
||||
binding_profile=dict(default=None, type='dict')
|
||||
binding_profile=dict(default=None, type='dict'),
|
||||
dns_name=dict(type='str', default=None),
|
||||
dns_domain=dict(type='str', default=None)
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
@@ -312,6 +322,13 @@ class NetworkPortModule(OpenStackModule):
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def _is_dns_integration_enabled(self):
|
||||
""" Check if dns-integraton is enabled """
|
||||
for ext in self.conn.network.extensions():
|
||||
if ext.alias == 'dns-integration':
|
||||
return True
|
||||
return False
|
||||
|
||||
def _needs_update(self, port):
|
||||
"""Check for differences in the updatable values.
|
||||
|
||||
@@ -324,10 +341,18 @@ class NetworkPortModule(OpenStackModule):
|
||||
'binding:vnic_type',
|
||||
'port_security_enabled',
|
||||
'binding:profile']
|
||||
compare_dns = ['dns_name', 'dns_domain']
|
||||
compare_list_dict = ['allowed_address_pairs',
|
||||
'extra_dhcp_opts']
|
||||
compare_list = ['security_groups']
|
||||
|
||||
if self.conn.has_service('dns') and \
|
||||
self._is_dns_integration_enabled():
|
||||
for key in compare_dns:
|
||||
if self.params[key] is not None and \
|
||||
self.params[key] != port[key]:
|
||||
return True
|
||||
|
||||
for key in compare_simple:
|
||||
if self.params[key] is not None and self.params[key] != port[key]:
|
||||
return True
|
||||
@@ -340,11 +365,11 @@ class NetworkPortModule(OpenStackModule):
|
||||
|
||||
for key in compare_list_dict:
|
||||
if not self.params[key]:
|
||||
if port[key]:
|
||||
if port.get(key):
|
||||
return True
|
||||
|
||||
if self.params[key]:
|
||||
if not port[key]:
|
||||
if not port.get(key):
|
||||
return True
|
||||
|
||||
# sort dicts in list
|
||||
@@ -410,6 +435,11 @@ class NetworkPortModule(OpenStackModule):
|
||||
'binding:vnic_type',
|
||||
'port_security_enabled',
|
||||
'binding:profile']
|
||||
|
||||
if self.conn.has_service('dns') and \
|
||||
self._is_dns_integration_enabled():
|
||||
optional_parameters.extend(['dns_name', 'dns_domain'])
|
||||
|
||||
for optional_param in optional_parameters:
|
||||
if self.params[optional_param] is not None:
|
||||
port_kwargs[optional_param] = self.params[optional_param]
|
||||
@@ -473,12 +503,14 @@ class NetworkPortModule(OpenStackModule):
|
||||
msg="Specified network was not found."
|
||||
)
|
||||
|
||||
port = self.conn.create_port(network_id, **port_kwargs)
|
||||
port_kwargs['network_id'] = network_id
|
||||
port = self.conn.network.create_port(**port_kwargs)
|
||||
changed = True
|
||||
else:
|
||||
if self._needs_update(port):
|
||||
port_kwargs = self._compose_port_args()
|
||||
port = self.conn.update_port(port['id'], **port_kwargs)
|
||||
port = self.conn.network.update_port(port['id'],
|
||||
**port_kwargs)
|
||||
changed = True
|
||||
self.exit_json(changed=changed, id=port['id'], port=port)
|
||||
|
||||
|
||||
@@ -188,6 +188,7 @@ class NetworkPortInfoModule(OpenStackModule):
|
||||
filters=dict(type='dict', required=False),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
deprecated_names = ('openstack.cloud.port_facts')
|
||||
|
||||
@@ -69,6 +69,9 @@ options:
|
||||
loadbalancer:
|
||||
description: Number of load balancers to allow.
|
||||
type: int
|
||||
metadata_items:
|
||||
description: Number of metadata items allowed per instance.
|
||||
type: int
|
||||
network:
|
||||
description: Number of networks to allow.
|
||||
type: int
|
||||
@@ -125,7 +128,7 @@ options:
|
||||
volumes_types:
|
||||
description:
|
||||
- Per-driver volume count quotas. Keys should be
|
||||
prefixed with C(gigabytes_) values should be ints.
|
||||
prefixed with C(volumes_) values should be ints.
|
||||
type: dict
|
||||
project:
|
||||
description: Unused, kept for compatability
|
||||
@@ -183,6 +186,7 @@ EXAMPLES = '''
|
||||
instances: "{{ item.instances }}"
|
||||
key_pairs: "{{ item.key_pairs }}"
|
||||
loadbalancer: "{{ item.loadbalancer }}"
|
||||
metadata_items: "{{ item.metadata_items }}"
|
||||
per_volume_gigabytes: "{{ item.per_volume_gigabytes }}"
|
||||
pool: "{{ item.pool }}"
|
||||
port: "{{ item.port }}"
|
||||
@@ -277,6 +281,7 @@ class QuotaModule(OpenStackModule):
|
||||
instances=dict(required=False, type='int', default=None),
|
||||
key_pairs=dict(required=False, type='int', default=None, no_log=False),
|
||||
loadbalancer=dict(required=False, type='int', default=None),
|
||||
metadata_items=dict(required=False, type='int', default=None),
|
||||
network=dict(required=False, type='int', default=None),
|
||||
per_volume_gigabytes=dict(required=False, type='int', default=None),
|
||||
pool=dict(required=False, type='int', default=None),
|
||||
|
||||
@@ -211,13 +211,7 @@ router:
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
|
||||
ROUTER_INTERFACE_OWNERS = set([
|
||||
'network:router_interface',
|
||||
'network:router_interface_distributed',
|
||||
'network:ha_router_replicated_interface'
|
||||
])
|
||||
import itertools
|
||||
|
||||
|
||||
class RouterModule(OpenStackModule):
|
||||
@@ -232,14 +226,16 @@ class RouterModule(OpenStackModule):
|
||||
project=dict(default=None)
|
||||
)
|
||||
|
||||
def _router_internal_interfaces(self, router):
|
||||
for port in self.conn.list_router_interfaces(router, 'internal'):
|
||||
if port['device_owner'] in ROUTER_INTERFACE_OWNERS:
|
||||
yield port
|
||||
def _get_subnet_ids_from_ports(self, ports):
|
||||
return [fixed_ip['subnet_id'] for fixed_ip in
|
||||
itertools.chain.from_iterable(port['fixed_ips'] for port in ports if 'fixed_ips' in port)]
|
||||
|
||||
def _needs_update(self, router, network, internal_subnet_ids, internal_port_ids, filters=None):
|
||||
"""Decide if the given router needs an update.
|
||||
"""
|
||||
def _needs_update(self, router, net,
|
||||
missing_port_ids,
|
||||
requested_subnet_ids,
|
||||
existing_subnet_ids,
|
||||
router_ifs_cfg):
|
||||
"""Decide if the given router needs an update."""
|
||||
if router['admin_state_up'] != self.params['admin_state_up']:
|
||||
return True
|
||||
if router['external_gateway_info']:
|
||||
@@ -247,68 +243,76 @@ class RouterModule(OpenStackModule):
|
||||
if self.params['enable_snat'] is not None:
|
||||
if router['external_gateway_info'].get('enable_snat', True) != self.params['enable_snat']:
|
||||
return True
|
||||
if network:
|
||||
if net:
|
||||
if not router['external_gateway_info']:
|
||||
return True
|
||||
elif router['external_gateway_info']['network_id'] != network['id']:
|
||||
elif router['external_gateway_info']['network_id'] != net['id']:
|
||||
return True
|
||||
|
||||
# check external interfaces
|
||||
if self.params['external_fixed_ips']:
|
||||
for new_iface in self.params['external_fixed_ips']:
|
||||
subnet = self.conn.get_subnet(new_iface['subnet'], filters)
|
||||
exists = False
|
||||
# check if external_fixed_ip has to be added
|
||||
for external_fixed_ip in router_ifs_cfg['external_fixed_ips']:
|
||||
exists = False
|
||||
|
||||
# compare the requested interface with existing, looking for an existing match
|
||||
for existing_iface in router['external_gateway_info']['external_fixed_ips']:
|
||||
if existing_iface['subnet_id'] == subnet['id']:
|
||||
if 'ip' in new_iface:
|
||||
if existing_iface['ip_address'] == new_iface['ip']:
|
||||
# compare the requested interface with existing, looking for an existing match
|
||||
for existing_if in router['external_gateway_info']['external_fixed_ips']:
|
||||
if existing_if['subnet_id'] == external_fixed_ip['subnet_id']:
|
||||
if 'ip' in external_fixed_ip:
|
||||
if existing_if['ip_address'] == external_fixed_ip['ip']:
|
||||
# both subnet id and ip address match
|
||||
exists = True
|
||||
break
|
||||
else:
|
||||
# only the subnet was given, so ip doesn't matter
|
||||
exists = True
|
||||
break
|
||||
|
||||
# this interface isn't present on the existing router
|
||||
if not exists:
|
||||
return True
|
||||
|
||||
# check if external_fixed_ip has to be removed
|
||||
if router_ifs_cfg['external_fixed_ips']:
|
||||
for external_fixed_ip in router['external_gateway_info']['external_fixed_ips']:
|
||||
obsolete = True
|
||||
|
||||
# compare the existing interface with requested, looking for an requested match
|
||||
for requested_if in router_ifs_cfg['external_fixed_ips']:
|
||||
if external_fixed_ip['subnet_id'] == requested_if['subnet_id']:
|
||||
if 'ip' in requested_if:
|
||||
if external_fixed_ip['ip_address'] == requested_if['ip']:
|
||||
# both subnet id and ip address match
|
||||
exists = True
|
||||
obsolete = False
|
||||
break
|
||||
else:
|
||||
# only the subnet was given, so ip doesn't matter
|
||||
exists = True
|
||||
obsolete = False
|
||||
break
|
||||
|
||||
# this interface isn't present on the existing router
|
||||
if not exists:
|
||||
if obsolete:
|
||||
return True
|
||||
|
||||
# check internal interfaces
|
||||
if self.params['interfaces']:
|
||||
existing_subnet_ids = []
|
||||
for port in self._router_internal_interfaces(router):
|
||||
if 'fixed_ips' in port:
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
existing_subnet_ids.append(fixed_ip['subnet_id'])
|
||||
|
||||
for iface in self.params['interfaces']:
|
||||
if isinstance(iface, dict):
|
||||
for p_id in internal_port_ids:
|
||||
p = self.conn.get_port(name_or_id=p_id)
|
||||
if 'fixed_ips' in p:
|
||||
for fip in p['fixed_ips']:
|
||||
internal_subnet_ids.append(fip['subnet_id'])
|
||||
|
||||
if set(internal_subnet_ids) != set(existing_subnet_ids):
|
||||
else:
|
||||
# no external fixed ips requested
|
||||
if router['external_gateway_info'] \
|
||||
and router['external_gateway_info']['external_fixed_ips'] \
|
||||
and len(router['external_gateway_info']['external_fixed_ips']) > 1:
|
||||
# but router has several external fixed ips
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _system_state_change(self, router, network, internal_ids, internal_portids, filters=None):
|
||||
"""Check if the system state would be changed."""
|
||||
state = self.params['state']
|
||||
if state == 'absent' and router:
|
||||
# check if internal port has to be added
|
||||
if router_ifs_cfg['internal_ports_missing']:
|
||||
return True
|
||||
if state == 'present':
|
||||
if not router:
|
||||
return True
|
||||
return self._needs_update(router, network, internal_ids, internal_portids, filters)
|
||||
|
||||
if missing_port_ids:
|
||||
return True
|
||||
|
||||
# check if internal subnet has to be added or removed
|
||||
if set(requested_subnet_ids) != set(existing_subnet_ids):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _build_kwargs(self, router, network):
|
||||
def _build_kwargs(self, router, net):
|
||||
kwargs = {
|
||||
'admin_state_up': self.params['admin_state_up'],
|
||||
}
|
||||
@@ -318,8 +322,8 @@ class RouterModule(OpenStackModule):
|
||||
else:
|
||||
kwargs['name'] = self.params['name']
|
||||
|
||||
if network:
|
||||
kwargs['ext_gateway_net_id'] = network['id']
|
||||
if net:
|
||||
kwargs['ext_gateway_net_id'] = net['id']
|
||||
# can't send enable_snat unless we have a network
|
||||
if self.params.get('enable_snat') is not None:
|
||||
kwargs['enable_snat'] = self.params['enable_snat']
|
||||
@@ -332,56 +336,83 @@ class RouterModule(OpenStackModule):
|
||||
if 'ip' in iface:
|
||||
d['ip_address'] = iface['ip']
|
||||
kwargs['ext_fixed_ips'].append(d)
|
||||
else:
|
||||
# no external fixed ips requested
|
||||
if router \
|
||||
and router['external_gateway_info'] \
|
||||
and router['external_gateway_info']['external_fixed_ips'] \
|
||||
and len(router['external_gateway_info']['external_fixed_ips']) > 1:
|
||||
# but router has several external fixed ips
|
||||
# keep first external fixed ip only
|
||||
fip = router['external_gateway_info']['external_fixed_ips'][0]
|
||||
kwargs['ext_fixed_ips'] = [fip]
|
||||
|
||||
return kwargs
|
||||
|
||||
def _validate_subnets(self, filters=None):
|
||||
external_subnet_ids = []
|
||||
internal_subnet_ids = []
|
||||
internal_port_ids = []
|
||||
existing_port_ips = []
|
||||
def _build_router_interface_config(self, filters=None):
|
||||
external_fixed_ips = []
|
||||
internal_subnets = []
|
||||
internal_ports = []
|
||||
internal_ports_missing = []
|
||||
|
||||
# Build external interface configuration
|
||||
if self.params['external_fixed_ips']:
|
||||
for iface in self.params['external_fixed_ips']:
|
||||
subnet = self.conn.get_subnet(iface['subnet'])
|
||||
subnet = self.conn.get_subnet(iface['subnet'], filters)
|
||||
if not subnet:
|
||||
self.fail_json(msg='subnet %s not found' % iface['subnet'])
|
||||
external_subnet_ids.append(subnet['id'])
|
||||
self.fail(msg='subnet %s not found' % iface['subnet'])
|
||||
new_external_fixed_ip = {'subnet_name': subnet.name, 'subnet_id': subnet.id}
|
||||
if 'ip' in iface:
|
||||
new_external_fixed_ip['ip'] = iface['ip']
|
||||
external_fixed_ips.append(new_external_fixed_ip)
|
||||
|
||||
# Build internal interface configuration
|
||||
if self.params['interfaces']:
|
||||
internal_ips = []
|
||||
for iface in self.params['interfaces']:
|
||||
if isinstance(iface, str):
|
||||
subnet = self.conn.get_subnet(iface, filters)
|
||||
if not subnet:
|
||||
self.fail(msg='subnet %s not found' % iface)
|
||||
internal_subnet_ids.append(subnet['id'])
|
||||
internal_subnets.append(subnet)
|
||||
|
||||
elif isinstance(iface, dict):
|
||||
subnet = self.conn.get_subnet(iface['subnet'], filters)
|
||||
if not subnet:
|
||||
self.fail(msg='subnet %s not found' % iface['subnet'])
|
||||
|
||||
net = self.conn.get_network(iface['net'])
|
||||
if not net:
|
||||
self.fail(msg='net %s not found' % iface['net'])
|
||||
|
||||
if "portip" not in iface:
|
||||
internal_subnet_ids.append(subnet['id'])
|
||||
# portip not set, add any ip from subnet
|
||||
internal_subnets.append(subnet)
|
||||
elif not iface['portip']:
|
||||
self.fail(msg='put an ip in portip or remove it from list to assign default port to router')
|
||||
# portip is set but has invalid value
|
||||
self.fail(msg='put an ip in portip or remove it from list to assign default port to router')
|
||||
else:
|
||||
# portip has valid value
|
||||
# look for ports whose fixed_ips.ip_address matchs portip
|
||||
for existing_port in self.conn.list_ports(filters={'network_id': net.id}):
|
||||
for fixed_ip in existing_port['fixed_ips']:
|
||||
if iface['portip'] == fixed_ip['ip_address']:
|
||||
internal_port_ids.append(existing_port.id)
|
||||
existing_port_ips.append(fixed_ip['ip_address'])
|
||||
if iface['portip'] not in existing_port_ips:
|
||||
p = self.conn.create_port(network_id=net.id, fixed_ips=[
|
||||
{
|
||||
'ip_address': iface['portip'],
|
||||
'subnet_id': subnet.id
|
||||
}
|
||||
])
|
||||
if p:
|
||||
internal_port_ids.append(p.id)
|
||||
# portip exists in net already
|
||||
internal_ports.append(existing_port)
|
||||
internal_ips.append(fixed_ip['ip_address'])
|
||||
if iface['portip'] not in internal_ips:
|
||||
# no port with portip exists hence create a new port
|
||||
internal_ports_missing.append({
|
||||
'network_id': net.id,
|
||||
'fixed_ips': [{'ip_address': iface['portip'], 'subnet_id': subnet.id}]
|
||||
})
|
||||
|
||||
return external_subnet_ids, internal_subnet_ids, internal_port_ids
|
||||
return {
|
||||
'external_fixed_ips': external_fixed_ips,
|
||||
'internal_subnets': internal_subnets,
|
||||
'internal_ports': internal_ports,
|
||||
'internal_ports_missing': internal_ports_missing
|
||||
}
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -391,7 +422,7 @@ class RouterModule(OpenStackModule):
|
||||
project = self.params['project']
|
||||
|
||||
if self.params['external_fixed_ips'] and not network:
|
||||
self.fail_json(msg='network is required when supplying external_fixed_ips')
|
||||
self.fail(msg='network is required when supplying external_fixed_ips')
|
||||
|
||||
if project is not None:
|
||||
proj = self.conn.get_project(project)
|
||||
@@ -412,67 +443,123 @@ class RouterModule(OpenStackModule):
|
||||
|
||||
# Validate and cache the subnet IDs so we can avoid duplicate checks
|
||||
# and expensive API calls.
|
||||
external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters)
|
||||
router_ifs_cfg = self._build_router_interface_config(filters)
|
||||
requested_subnet_ids = [subnet.id for subnet in router_ifs_cfg['internal_subnets']] + \
|
||||
self._get_subnet_ids_from_ports(router_ifs_cfg['internal_ports'])
|
||||
requested_port_ids = [i['id'] for i in router_ifs_cfg['internal_ports']]
|
||||
|
||||
if router:
|
||||
router_ifs_internal = self.conn.list_router_interfaces(router, 'internal')
|
||||
existing_subnet_ids = self._get_subnet_ids_from_ports(router_ifs_internal)
|
||||
obsolete_subnet_ids = set(existing_subnet_ids) - set(requested_subnet_ids)
|
||||
existing_port_ids = [i['id'] for i in router_ifs_internal]
|
||||
|
||||
else:
|
||||
router_ifs_internal = []
|
||||
existing_subnet_ids = []
|
||||
obsolete_subnet_ids = []
|
||||
existing_port_ids = []
|
||||
|
||||
missing_port_ids = set(requested_port_ids) - set(existing_port_ids)
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(
|
||||
changed=self._system_state_change(router, net, subnet_internal_ids, internal_portids, filters)
|
||||
)
|
||||
# Check if the system state would be changed
|
||||
if state == 'absent' and router:
|
||||
changed = True
|
||||
elif state == 'absent' and not router:
|
||||
changed = False
|
||||
elif state == 'present' and not router:
|
||||
changed = True
|
||||
else: # if state == 'present' and router
|
||||
changed = self._needs_update(router, net,
|
||||
missing_port_ids,
|
||||
requested_subnet_ids,
|
||||
existing_subnet_ids,
|
||||
router_ifs_cfg)
|
||||
self.exit_json(changed=changed)
|
||||
|
||||
if state == 'present':
|
||||
changed = False
|
||||
|
||||
if not router:
|
||||
changed = True
|
||||
|
||||
kwargs = self._build_kwargs(router, net)
|
||||
if project_id:
|
||||
kwargs['project_id'] = project_id
|
||||
router = self.conn.create_router(**kwargs)
|
||||
for int_s_id in subnet_internal_ids:
|
||||
self.conn.add_router_interface(router, subnet_id=int_s_id)
|
||||
# add interface by port id as well
|
||||
for int_p_id in internal_portids:
|
||||
self.conn.add_router_interface(router, port_id=int_p_id)
|
||||
changed = True
|
||||
|
||||
# add interface by subnet id, because user did not specify a port id
|
||||
for subnet in router_ifs_cfg['internal_subnets']:
|
||||
self.conn.add_router_interface(router, subnet_id=subnet.id)
|
||||
|
||||
# add interface by port id if user did specify a valid port id
|
||||
for port in router_ifs_cfg['internal_ports']:
|
||||
self.conn.add_router_interface(router, port_id=port.id)
|
||||
|
||||
# add port and interface if user did specify an ip address but port is missing yet
|
||||
for missing_internal_port in router_ifs_cfg['internal_ports_missing']:
|
||||
p = self.conn.create_port(**missing_internal_port)
|
||||
if p:
|
||||
self.conn.add_router_interface(router, port_id=p.id)
|
||||
|
||||
else:
|
||||
if self._needs_update(router, net, subnet_internal_ids, internal_portids, filters):
|
||||
if self._needs_update(router, net,
|
||||
missing_port_ids,
|
||||
requested_subnet_ids,
|
||||
existing_subnet_ids,
|
||||
router_ifs_cfg):
|
||||
changed = True
|
||||
kwargs = self._build_kwargs(router, net)
|
||||
updated_router = self.conn.update_router(**kwargs)
|
||||
|
||||
# Protect against update_router() not actually
|
||||
# updating the router.
|
||||
# Protect against update_router() not actually updating the router.
|
||||
if not updated_router:
|
||||
changed = False
|
||||
|
||||
# On a router update, if any internal interfaces were supplied,
|
||||
# just detach all existing internal interfaces and attach the new.
|
||||
if internal_portids or subnet_internal_ids:
|
||||
else:
|
||||
router = updated_router
|
||||
ports = self._router_internal_interfaces(router)
|
||||
for port in ports:
|
||||
self.conn.remove_router_interface(router, port_id=port['id'])
|
||||
if internal_portids:
|
||||
external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters)
|
||||
for int_p_id in internal_portids:
|
||||
self.conn.add_router_interface(router, port_id=int_p_id)
|
||||
changed = True
|
||||
if subnet_internal_ids:
|
||||
for s_id in subnet_internal_ids:
|
||||
self.conn.add_router_interface(router, subnet_id=s_id)
|
||||
|
||||
# delete internal subnets i.e. ports
|
||||
if obsolete_subnet_ids:
|
||||
for port in router_ifs_internal:
|
||||
if 'fixed_ips' in port:
|
||||
for fip in port['fixed_ips']:
|
||||
if fip['subnet_id'] in obsolete_subnet_ids:
|
||||
self.conn.remove_router_interface(router, port_id=port['id'])
|
||||
changed = True
|
||||
|
||||
# add new internal interface by subnet id, because user did not specify a port id
|
||||
for subnet in router_ifs_cfg['internal_subnets']:
|
||||
if subnet.id not in existing_subnet_ids:
|
||||
self.conn.add_router_interface(router, subnet_id=subnet.id)
|
||||
changed = True
|
||||
|
||||
# add new internal interface by port id if user did specify a valid port id
|
||||
for port_id in missing_port_ids:
|
||||
self.conn.add_router_interface(router, port_id=port_id)
|
||||
changed = True
|
||||
|
||||
self.exit(changed=changed, router=router, id=router['id'])
|
||||
# add new port and new internal interface if user did specify an ip address but port is missing yet
|
||||
for missing_internal_port in router_ifs_cfg['internal_ports_missing']:
|
||||
p = self.conn.create_port(**missing_internal_port)
|
||||
if p:
|
||||
self.conn.add_router_interface(router, port_id=p.id)
|
||||
changed = True
|
||||
|
||||
self.exit_json(changed=changed, router=router)
|
||||
|
||||
elif state == 'absent':
|
||||
if not router:
|
||||
self.exit(changed=False)
|
||||
self.exit_json(changed=False)
|
||||
else:
|
||||
# We need to detach all internal interfaces on a router before
|
||||
# we will be allowed to delete it.
|
||||
ports = self._router_internal_interfaces(router)
|
||||
router_id = router['id']
|
||||
for port in ports:
|
||||
# We need to detach all internal interfaces on a router
|
||||
# before we will be allowed to delete it. Deletion can
|
||||
# still fail if e.g. floating ips are attached to the
|
||||
# router.
|
||||
for port in router_ifs_internal:
|
||||
self.conn.remove_router_interface(router, port_id=port['id'])
|
||||
self.conn.delete_router(router_id)
|
||||
self.exit_json(changed=True)
|
||||
self.conn.delete_router(router['id'])
|
||||
self.exit_json(changed=True, router=router)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -49,6 +49,7 @@ options:
|
||||
description:
|
||||
- A list of tags to filter the list result by. Resources that match all tags in this list will be returned.
|
||||
type: list
|
||||
elements: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
@@ -155,6 +156,9 @@ class RouterInfoModule(OpenStackModule):
|
||||
name=dict(required=False, default=None),
|
||||
filters=dict(required=False, type='dict', default=None)
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
@@ -148,6 +148,9 @@ class SecurityGroupInfoModule(OpenStackModule):
|
||||
not_tags=dict(required=False, type='list', elements='str'),
|
||||
not_any_tags=dict(required=False, type='list', elements='str')
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
description = self.params['description']
|
||||
|
||||
@@ -177,7 +177,8 @@ class SecurityGroupRuleInfoModule(OpenStackModule):
|
||||
module_kwargs = dict(
|
||||
mutually_exclusive=[
|
||||
['remote_ip_prefix', 'remote_group'],
|
||||
]
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -46,6 +46,12 @@ options:
|
||||
description:
|
||||
- Admin password for server to rebuild
|
||||
type: str
|
||||
all_projects:
|
||||
description:
|
||||
- Whether to search for server in all projects or just the current
|
||||
auth scoped project.
|
||||
type: bool
|
||||
default: 'no'
|
||||
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
@@ -120,6 +126,7 @@ class ServerActionModule(OpenStackModule):
|
||||
'rebuild', 'shelve', 'shelve_offload', 'unshelve']),
|
||||
image=dict(required=False, type='str'),
|
||||
admin_password=dict(required=False, type='str', no_log=True),
|
||||
all_projects=dict(required=False, type='bool', default=False),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
required_if=[('action', 'rebuild', ['image'])],
|
||||
@@ -137,7 +144,10 @@ class ServerActionModule(OpenStackModule):
|
||||
|
||||
def _preliminary_checks(self):
|
||||
# Using Munch object for getting information about a server
|
||||
os_server = self.conn.get_server(self.params['server'])
|
||||
os_server = self.conn.get_server(
|
||||
self.params['server'],
|
||||
all_projects=self.params['all_projects'],
|
||||
)
|
||||
if not os_server:
|
||||
self.fail_json(msg='Could not find server %s' % self.params['server'])
|
||||
# check mode
|
||||
@@ -193,8 +203,9 @@ class ServerActionModule(OpenStackModule):
|
||||
|
||||
def _wait(self, os_server):
|
||||
"""Wait for the server to reach the desired state for the given action."""
|
||||
# Using Server object for wait_for_server function
|
||||
server = self.conn.compute.find_server(self.params['server'])
|
||||
# The wait_for_server function needs a Server object instead of the
|
||||
# Munch object returned by self.conn.get_server
|
||||
server = self.conn.compute.get_server(os_server['id'])
|
||||
states = _action_map[self.params['action']]
|
||||
|
||||
try:
|
||||
|
||||
@@ -70,6 +70,9 @@ class ServerInfoModule(OpenStackModule):
|
||||
filters=dict(required=False, type='dict', default=None),
|
||||
all_projects=dict(required=False, type='bool', default=False),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
@@ -89,6 +89,9 @@ class ServerVolumeModule(OpenStackModule):
|
||||
server = self.conn.get_server(self.params['server'])
|
||||
volume = self.conn.get_volume(self.params['volume'])
|
||||
|
||||
if not server:
|
||||
self.fail(msg='server %s is not found' % self.params['server'])
|
||||
|
||||
if not volume:
|
||||
self.fail(msg='volume %s is not found' % self.params['volume'])
|
||||
|
||||
|
||||
@@ -80,6 +80,9 @@ class StackInfoModule(OpenStackModule):
|
||||
project_id=dict(required=False, type='str'),
|
||||
owner_id=dict(required=False, type='str')
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
data = []
|
||||
|
||||
@@ -39,7 +39,7 @@ options:
|
||||
ip_version:
|
||||
description:
|
||||
- The IP version of the subnet 4 or 6
|
||||
default: 4
|
||||
default: '4'
|
||||
type: str
|
||||
choices: ['4', '6']
|
||||
enable_dhcp:
|
||||
|
||||
345
plugins/modules/subnet_pool.py
Normal file
345
plugins/modules/subnet_pool.py
Normal file
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/python
|
||||
# coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2021 by Uemit Seren <uemit.seren@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: subnet_pool
|
||||
short_description: Create or delete subnet pools from OpenStack
|
||||
author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Create or Delete subnet pools from OpenStack.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the resource
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name to be give to the subnet pool
|
||||
required: true
|
||||
type: str
|
||||
project:
|
||||
description:
|
||||
- Unique name or ID of the project.
|
||||
type: str
|
||||
prefixes:
|
||||
description:
|
||||
- Set subnet pool prefixes (in CIDR notation)
|
||||
type: list
|
||||
elements: str
|
||||
minimum_prefix_length:
|
||||
description:
|
||||
- The minimum prefix length that can be allocated from the subnet pool.
|
||||
required: False
|
||||
type: int
|
||||
maximum_prefix_length:
|
||||
description:
|
||||
- The maximum prefix length that can be allocated from the subnet pool.
|
||||
required: False
|
||||
type: int
|
||||
default_prefix_length:
|
||||
description:
|
||||
- The length of the prefix to allocate when the cidr or prefixlen attributes
|
||||
are omitted when creating a subnet
|
||||
type: int
|
||||
required: False
|
||||
address_scope:
|
||||
description:
|
||||
- Set address scope (ID or name) associated with the subnet pool
|
||||
type: str
|
||||
required: False
|
||||
is_default:
|
||||
description:
|
||||
- Whether this subnet pool is by default
|
||||
type: bool
|
||||
default: 'no'
|
||||
description:
|
||||
description: The subnet pool description
|
||||
type: str
|
||||
required: False
|
||||
default_quota:
|
||||
description:
|
||||
- A per-project quota on the prefix space that can be allocated
|
||||
from the subnet pool for project subnets
|
||||
required: False
|
||||
type: int
|
||||
shared:
|
||||
description:
|
||||
- Whether this subnet pool is shared or not.
|
||||
type: bool
|
||||
default: 'no'
|
||||
extra_specs:
|
||||
description:
|
||||
- Dictionary with extra key/value pairs passed to the API
|
||||
required: false
|
||||
default: {}
|
||||
type: dict
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
|
||||
extends_documentation_fragment:
|
||||
- openstack.cloud.openstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create an subnet pool.
|
||||
- openstack.cloud.subnet_pool:
|
||||
cloud: mycloud
|
||||
state: present
|
||||
name: my_subnet_pool
|
||||
prefixes:
|
||||
- 10.10.10.0/24
|
||||
|
||||
# Create a subnet pool for a given project.
|
||||
- openstack.cloud.subnet_pool:
|
||||
cloud: mycloud
|
||||
state: present
|
||||
name: my_subnet_pool
|
||||
project: myproj
|
||||
prefixes:
|
||||
- 10.10.10.0/24
|
||||
|
||||
# Create a shared and default subnet pool in existing address scope
|
||||
- openstack.cloud.subnet_pool:
|
||||
cloud: mycloud
|
||||
state: present
|
||||
name: my_subnet_pool
|
||||
address_scope: my_adress_scope
|
||||
is_default: True
|
||||
default_quota: 10
|
||||
maximum_prefix_length: 32
|
||||
minimum_prefix_length: 8
|
||||
default_prefix_length: 24
|
||||
shared: True
|
||||
prefixes:
|
||||
- 10.10.10.0/8
|
||||
|
||||
# Delete subnet poool.
|
||||
- openstack.cloud.subnet_pool:
|
||||
cloud: mycloud
|
||||
state: absent
|
||||
name: my_subnet_pool
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
subnet_pool:
|
||||
description: Dictionary describing the subnet pool.
|
||||
returned: On success when I(state) is 'present'
|
||||
type: complex
|
||||
contains:
|
||||
id:
|
||||
description: Subnet Pool ID.
|
||||
type: str
|
||||
sample: "474acfe5-be34-494c-b339-50f06aa143e4"
|
||||
name:
|
||||
description: Subnet Pool name.
|
||||
type: str
|
||||
sample: "my_subnet_pool"
|
||||
project_id:
|
||||
description: The ID of the project.
|
||||
type: str
|
||||
sample: "861174b82b43463c9edc5202aadc60ef"
|
||||
ip_version:
|
||||
description: The IP version of the subnet pool 4 or 6.
|
||||
type: int
|
||||
sample: 4
|
||||
is_shared:
|
||||
description: Indicates whether this subnet pool is shared across all projects.
|
||||
type: bool
|
||||
sample: false
|
||||
is_default:
|
||||
description: Indicates whether this is the default subnet pool.
|
||||
type: bool
|
||||
sample: false
|
||||
address_scope_id:
|
||||
description: The address scope ID.
|
||||
type: str
|
||||
sample: "861174b82b43463c9edc5202aadc60ef"
|
||||
created_at:
|
||||
description: Timestamp when the subnet pool was created.
|
||||
type: str
|
||||
sample: ""
|
||||
default_prefix_length:
|
||||
description:
|
||||
- The length of the prefix to allocate when the cidr or prefixlen
|
||||
attributes are omitted when creating a subnet
|
||||
type: int
|
||||
sample: 32
|
||||
default_quota:
|
||||
description:
|
||||
- The per-project quota on the prefix space that can be allocated
|
||||
from the subnet pool for project subnets.
|
||||
type: int
|
||||
sample: 22
|
||||
description:
|
||||
description: The subnet pool description.
|
||||
type: str
|
||||
sample: "My test subnet pool."
|
||||
maximum_prefix_length:
|
||||
description: The maximum prefix length that can be allocated from the subnet pool.
|
||||
type: int
|
||||
sample: 22
|
||||
minimum_prefix_length:
|
||||
description: The minimum prefix length that can be allocated from the subnet pool.
|
||||
type: int
|
||||
sample: 8
|
||||
prefixes:
|
||||
description: A list of subnet prefixes that are assigned to the subnet pool.
|
||||
type: list
|
||||
sample: ['10.10.20.0/24', '10.20.10.0/24']
|
||||
revision_number:
|
||||
description: Revision number of the subnet pool.
|
||||
type: int
|
||||
sample: 5
|
||||
updated_at:
|
||||
description: Timestamp when the subnet pool was last updated.
|
||||
type: str
|
||||
sample:
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
|
||||
class SubnetPoolModule(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
name=dict(required=True),
|
||||
shared=dict(default=False, type='bool'),
|
||||
minimum_prefix_length=dict(default=None, type='int'),
|
||||
maximum_prefix_length=dict(default=None, type='int'),
|
||||
default_prefix_length=dict(default=None, type='int'),
|
||||
description=dict(default=None, type='str'),
|
||||
default_quota=dict(default=None, type='int'),
|
||||
prefixes=dict(type='list', elements='str'),
|
||||
is_default=dict(default=False, type='bool'),
|
||||
address_scope=dict(default=None),
|
||||
project=dict(default=None),
|
||||
extra_specs=dict(type='dict', default=dict())
|
||||
)
|
||||
|
||||
def _needs_update(self, subnet_pool):
|
||||
"""Check for differences in the updatable values.
|
||||
|
||||
NOTE: We don't currently allow name updates.
|
||||
"""
|
||||
compare_simple = ['is_default',
|
||||
'minimum_prefix_length',
|
||||
'maximum_prefix_length',
|
||||
'default_prefix_length',
|
||||
'description',
|
||||
'default_quota']
|
||||
compare_list = ['prefixes']
|
||||
|
||||
for key in compare_simple:
|
||||
if self.params[key] is not None and self.params[key] != subnet_pool[key]:
|
||||
return True
|
||||
for key in compare_list:
|
||||
if (
|
||||
self.params[key] is not None
|
||||
and set(self.params[key]) != set(subnet_pool[key])
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _system_state_change(self, subnet_pool, filters=None):
|
||||
"""Check if the system state would be changed."""
|
||||
state = self.params['state']
|
||||
if state == 'absent' and subnet_pool:
|
||||
return True
|
||||
if state == 'present':
|
||||
if not subnet_pool:
|
||||
return True
|
||||
return self._needs_update(subnet_pool, filters)
|
||||
return False
|
||||
|
||||
def _compose_subnet_pool_args(self):
|
||||
subnet_pool_kwargs = {}
|
||||
optional_parameters = ['name',
|
||||
'minimum_prefix_length',
|
||||
'maximum_prefix_length',
|
||||
'default_prefix_length',
|
||||
'description',
|
||||
'is_default',
|
||||
'default_quota',
|
||||
'prefixes']
|
||||
|
||||
for optional_param in optional_parameters:
|
||||
if self.params[optional_param] is not None:
|
||||
subnet_pool_kwargs[optional_param] = self.params[optional_param]
|
||||
|
||||
return subnet_pool_kwargs
|
||||
|
||||
def run(self):
|
||||
|
||||
state = self.params['state']
|
||||
name = self.params['name']
|
||||
project = self.params['project']
|
||||
address_scope = self.params['address_scope']
|
||||
extra_specs = self.params['extra_specs']
|
||||
|
||||
if project is not None:
|
||||
proj = self.conn.get_project(project)
|
||||
if proj is None:
|
||||
self.fail(msg='Project %s could not be found' % project)
|
||||
project_id = proj['id']
|
||||
else:
|
||||
project_id = self.conn.current_project_id
|
||||
|
||||
address_scope_id = None
|
||||
if address_scope is not None:
|
||||
address_scope = self.conn.network.find_address_scope(name_or_id=address_scope)
|
||||
if address_scope is None:
|
||||
self.fail(msg='AddressScope %s could not be found' % address_scope)
|
||||
address_scope_id = address_scope['id']
|
||||
subnet_pool = self.conn.network.find_subnet_pool(name_or_id=name)
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(
|
||||
changed=self._system_state_change(subnet_pool)
|
||||
)
|
||||
|
||||
if state == 'present':
|
||||
changed = False
|
||||
|
||||
if not subnet_pool:
|
||||
kwargs = self._compose_subnet_pool_args()
|
||||
kwargs['address_scope_id'] = address_scope_id
|
||||
kwargs['project_id'] = project_id
|
||||
kwargs['is_shared'] = self.params['shared']
|
||||
dup_args = set(kwargs.keys()) & set(extra_specs.keys())
|
||||
if dup_args:
|
||||
raise ValueError('Duplicate key(s) {0} in extra_specs'
|
||||
.format(list(dup_args)))
|
||||
kwargs = dict(kwargs, **extra_specs)
|
||||
subnet_pool = self.conn.network.create_subnet_pool(**kwargs)
|
||||
changed = True
|
||||
else:
|
||||
if self._needs_update(subnet_pool):
|
||||
kwargs = self._compose_subnet_pool_args()
|
||||
subnet_pool = self.conn.network.update_subnet_pool(subnet_pool['id'], **kwargs)
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
self.exit_json(changed=changed, subnet_pool=subnet_pool, id=subnet_pool['id'])
|
||||
|
||||
elif state == 'absent':
|
||||
if not subnet_pool:
|
||||
self.exit(changed=False)
|
||||
else:
|
||||
self.conn.network.delete_subnet_pool(subnet_pool['id'])
|
||||
self.exit_json(changed=True)
|
||||
|
||||
|
||||
def main():
|
||||
module = SubnetPoolModule()
|
||||
module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -140,6 +140,9 @@ class SubnetInfoModule(OpenStackModule):
|
||||
name=dict(required=False, default=None, aliases=['subnet']),
|
||||
filters=dict(required=False, type='dict', default=None)
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
kwargs = self.check_versioned(
|
||||
|
||||
@@ -90,6 +90,9 @@ class VolumeBackupInfoModule(OpenStackModule):
|
||||
name=dict(required=False, type='str'),
|
||||
volume=dict(required=False, type='str')
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
name_filter = self.params['name']
|
||||
|
||||
@@ -124,6 +124,9 @@ class VolumeInfoModule(OpenStackModule):
|
||||
name=dict(type='str', required=False),
|
||||
status=dict(type='str', required=False),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
kwargs = self.check_versioned(
|
||||
|
||||
@@ -96,6 +96,9 @@ class VolumeSnapshotInfoModule(OpenStackModule):
|
||||
'deleting', 'error_deleting', 'rollbacking',
|
||||
'backing-up']),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
openstacksdk>=0.13
|
||||
openstacksdk>=0.36
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
test-requirements-2.11.txt
|
||||
1
tests/constraints-none.txt
Normal file
1
tests/constraints-none.txt
Normal file
@@ -0,0 +1 @@
|
||||
# No constraints are defined by default
|
||||
1
tests/constraints-openstacksdk-0.x.x.txt
Normal file
1
tests/constraints-openstacksdk-0.x.x.txt
Normal file
@@ -0,0 +1 @@
|
||||
openstacksdk<1.0.0
|
||||
1
tests/constraints-openstacksdk-1.x.x.txt
Normal file
1
tests/constraints-openstacksdk-1.x.x.txt
Normal file
@@ -0,0 +1 @@
|
||||
openstacksdk>=1.0.0
|
||||
@@ -1,11 +1,11 @@
|
||||
openstacksdk
|
||||
ansible<2.10
|
||||
pycodestyle
|
||||
ansible-core>=2.11.0,<2.12.0
|
||||
flake8
|
||||
galaxy-importer
|
||||
openstacksdk
|
||||
pycodestyle
|
||||
pylint
|
||||
voluptuous
|
||||
yamllint
|
||||
rstcheck
|
||||
ruamel.yaml
|
||||
#galaxy-importer # see https://review.opendev.org/#/c/743054
|
||||
tox
|
||||
voluptuous
|
||||
yamllint
|
||||
11
tests/requirements-ansible-2.12.txt
Normal file
11
tests/requirements-ansible-2.12.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
ansible-core>=2.12.0,<2.13.0
|
||||
flake8
|
||||
galaxy-importer
|
||||
openstacksdk
|
||||
pycodestyle
|
||||
pylint
|
||||
rstcheck
|
||||
ruamel.yaml
|
||||
tox
|
||||
voluptuous
|
||||
yamllint
|
||||
13
tests/requirements-ansible-2.9.txt
Normal file
13
tests/requirements-ansible-2.9.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
ansible>=2.9.0,<2.10.0
|
||||
flake8
|
||||
# galaxy-importer 0.3.2 moved from ansible 2.9 to ansible-core 2.11
|
||||
# Ref.: https://github.com/ansible/galaxy-importer/commit/98933547831922c45243f39d85eefe150b55fc36
|
||||
galaxy-importer==0.3.1
|
||||
openstacksdk
|
||||
pycodestyle
|
||||
pylint
|
||||
rstcheck
|
||||
ruamel.yaml
|
||||
tox
|
||||
voluptuous
|
||||
yamllint
|
||||
@@ -1,11 +1,11 @@
|
||||
openstacksdk
|
||||
ansible-core
|
||||
pycodestyle
|
||||
flake8
|
||||
galaxy-importer
|
||||
openstacksdk
|
||||
pycodestyle
|
||||
pylint
|
||||
voluptuous
|
||||
yamllint
|
||||
rstcheck
|
||||
ruamel.yaml
|
||||
#galaxy-importer # see https://review.opendev.org/#/c/743054
|
||||
tox
|
||||
voluptuous
|
||||
yamllint
|
||||
0
tests/sanity/ignore-2.13.txt
Normal file
0
tests/sanity/ignore-2.13.txt
Normal file
@@ -15,11 +15,5 @@
|
||||
|
||||
set -e
|
||||
|
||||
TOXDIR=${1:-.}
|
||||
######### Disbaled in https://review.opendev.org/#/c/743054
|
||||
|
||||
# galaxy_importer.main does not return non-zero error code on error
|
||||
#output=$(python -m galaxy_importer.main $TOXDIR/build_artifact/*)
|
||||
#if echo $output | grep ERROR: ; then
|
||||
# exit 1
|
||||
#fi
|
||||
TOXDIR="${1:-.}"
|
||||
python -m galaxy_importer.main "$TOXDIR/build_artifact/"*
|
||||
|
||||
@@ -19,6 +19,9 @@ echo "Executing ansible-test sanity checks in ${ANSIBLE_COLLECTIONS_PATH}"
|
||||
|
||||
trap "rm -rf ${ANSIBLE_COLLECTIONS_PATH}" err exit
|
||||
|
||||
PY_VER=$(python3 -c "from platform import python_version;print(python_version())" | cut -f 1,2 -d".")
|
||||
echo "Running test with Python version ${PY_VER}"
|
||||
|
||||
rm -rf "${ANSIBLE_COLLECTIONS_PATH}"
|
||||
mkdir -p ${ANSIBLE_COLLECTIONS_PATH}/ansible_collections/openstack/cloud
|
||||
cp -a ${TOXDIR}/{plugins,meta,scripts,tests,docs} ${ANSIBLE_COLLECTIONS_PATH}/ansible_collections/openstack/cloud
|
||||
@@ -27,7 +30,7 @@ echo "Running ansible-test with version:"
|
||||
ansible --version
|
||||
ansible-test sanity -v \
|
||||
--venv \
|
||||
--python 3.6 \
|
||||
--python ${PY_VER} \
|
||||
--skip-test metaclass-boilerplate \
|
||||
--skip-test future-import-boilerplate \
|
||||
plugins/ docs/ meta/ scripts/ tests/
|
||||
plugins/ docs/ meta/ scripts/
|
||||
|
||||
57
tox.ini
57
tox.ini
@@ -1,6 +1,6 @@
|
||||
[tox]
|
||||
minversion = 3.1
|
||||
envlist = pep8
|
||||
minversion = 3.18.0
|
||||
envlist = linters
|
||||
skipsdist = True
|
||||
ignore_basepython_conflict = True
|
||||
|
||||
@@ -15,13 +15,14 @@ setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
LANG=en_US.UTF-8
|
||||
LANGUAGE=en_US:en
|
||||
LC_ALL=C
|
||||
LC_ALL=en_US.utf-8
|
||||
OS_LOG_CAPTURE={env:OS_LOG_CAPTURE:true}
|
||||
OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:true}
|
||||
OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:true}
|
||||
pip: PIP_INSTALL={env:PIP_INSTALL:true}
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt}
|
||||
-r{toxinidir}/tests/requirements.txt
|
||||
pip: {toxinidir}
|
||||
|
||||
commands = stestr run {posargs}
|
||||
@@ -33,9 +34,10 @@ commands =
|
||||
|
||||
[testenv:build]
|
||||
deps =
|
||||
ansible-core
|
||||
galaxy-importer
|
||||
pbr
|
||||
ruamel.yaml
|
||||
ansible-core
|
||||
|
||||
commands =
|
||||
python {toxinidir}/tools/build.py
|
||||
@@ -57,18 +59,22 @@ commands =
|
||||
passenv = {[testenv:linters]passenv}
|
||||
commands = {[testenv:linters]commands}
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements-2.9.txt
|
||||
-c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt}
|
||||
-r{toxinidir}/tests/requirements-ansible-2.9.txt
|
||||
|
||||
[testenv:linters-2.11]
|
||||
passenv = {[testenv:linters]passenv}
|
||||
commands = {[testenv:linters]commands}
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements-2.11.txt
|
||||
-c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt}
|
||||
-r{toxinidir}/tests/requirements-ansible-2.11.txt
|
||||
|
||||
[testenv:venv]
|
||||
[testenv:linters-2.12]
|
||||
passenv = {[testenv:linters]passenv}
|
||||
commands = {[testenv:linters]commands}
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = {posargs}
|
||||
-c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt}
|
||||
-r{toxinidir}/tests/requirements-ansible-2.12.txt
|
||||
|
||||
[flake8]
|
||||
# W503 Is supposed to be off by default but in the latest pycodestyle isn't.
|
||||
@@ -92,22 +98,33 @@ deps =
|
||||
commands =
|
||||
/bin/bash {toxinidir}/ci/run-ansible-tests-collection.sh -e {envdir} {posargs}
|
||||
|
||||
# PIP job runs with Ansible-2.9
|
||||
[testenv:ansible-pip]
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements-2.9.txt
|
||||
{toxinidir}
|
||||
passenv = {[testenv:ansible]passenv}
|
||||
commands = {[testenv:ansible]commands}
|
||||
|
||||
[testenv:ansible-2.9]
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements-2.9.txt
|
||||
-c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt}
|
||||
-r{toxinidir}/tests/requirements-ansible-2.9.txt
|
||||
passenv = {[testenv:ansible]passenv}
|
||||
commands = {[testenv:ansible]commands}
|
||||
|
||||
[testenv:ansible-2.11]
|
||||
deps =
|
||||
-r{toxinidir}/test-requirements-2.11.txt
|
||||
-c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt}
|
||||
-r{toxinidir}/tests/requirements-ansible-2.11.txt
|
||||
passenv = {[testenv:ansible]passenv}
|
||||
commands = {[testenv:ansible]commands}
|
||||
|
||||
[testenv:ansible-2.12]
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:{toxinidir}/tests/constraints-none.txt}
|
||||
-r{toxinidir}/tests/requirements-ansible-2.12.txt
|
||||
passenv = {[testenv:ansible]passenv}
|
||||
commands = {[testenv:ansible]commands}
|
||||
|
||||
[testenv:galaxy-release]
|
||||
deps =
|
||||
ansible-core
|
||||
commands =
|
||||
/usr/bin/mkdir -p /tmp/collection_built
|
||||
/usr/bin/bash -ec 'rm -rf /tmp/collection_built/*'
|
||||
/usr/bin/sed -i "s/version:.*/version: {env:VERSION_TAG}/" {toxinidir}/galaxy.yml
|
||||
ansible-galaxy collection build {toxinidir} --output-path /tmp/collection_built/ --force
|
||||
ansible-galaxy collection publish /tmp/collection_built/openstack-cloud-{env:VERSION_TAG}.tar.gz --token {env:API_GALAXY_TOKEN}
|
||||
|
||||
Reference in New Issue
Block a user