60 Commits
1.1.0 ... 1.2.1

Author SHA1 Message Date
Sagi Shnaidman
e3317db56d Release 1.2.1 version of Openstack Ansible modules
Change-Id: Ia775e7df0ceb8ce0eef3f6d11b957d223729de57
2021-01-03 17:04:29 +02:00
Sagi Shnaidman
870f82d97b Decrease MTU in networks tests
Not all environments support MTU 1350, let's decrease it to 1250.
Change-Id: I6c03c0a1492394eb9fb5de8b0815b459437895cb
2020-12-31 14:07:56 +02:00
Polina Gubina
87c305907e Enable update for recordset and add tests for dns and recordset module
Update for recordset wasn't working properly and there are no tests
for dns and recordset modules, minor fix in dns_zone

Change-Id: I7f78f6038dfb858e795b1954eae11cff47f697ad
2020-12-29 11:34:27 +00:00
Zuul
c532560d1b Merge "Migrating subnet module from AnsibleModule to OpenStackModule" 2020-12-21 14:21:11 +00:00
anbanerj
ea1f1df805 Migrating subnet module from AnsibleModule to OpenStackModule
1. Created class SubnetModule with run method
2. Changed argument_spec from openstack_full_argument_spec to dict
3. Moved "netid != subnet['network_id']:" check inside "if network:" loop
4. Moved the methods using "module" inside SubnetModule class

Change-Id: I8f19359e70f8565ebfb096d30ab75e727f748be6
2020-12-18 13:49:52 +01:00
Zuul
40a32c1e8b Merge "Аdd designate to install with devstack" 2020-12-17 19:23:00 +00:00
Zuul
5881f5423a Merge "Separate volume tests from servers tests" 2020-12-17 15:06:07 +00:00
Sagi Shnaidman
51eba6de02 Аdd designate to install with devstack
Allow DNS tests
Change-Id: I215ce23bbfe68437523e5ee608508dfcc028731a
2020-12-17 06:22:56 +00:00
Zuul
93faf4f1c3 Merge "Add network tests for versioned args" 2020-12-16 17:11:08 +00:00
Zuul
c03284abec Merge "Migrating network from AnsibleModule to OpenStackModule" 2020-12-16 16:59:29 +00:00
frenzy_friday
49c95804ba Add network tests for versioned args
1. Updated Create network tasks with mtu and port security enabled params which are supported for sdk version >= 0.18
2. Added task to assert that network creation with new params fail if sdk version is lower than 0.18

Change-Id: I7d65d8553e820344f76cf1092e0a373c8100b7c8
2020-12-16 08:52:05 +00:00
Sagi Shnaidman
c8a5be6b30 Separate volume tests from servers tests
Tag volume tests in servers as "object" so we can skip them when
volumes are not available.
Change-Id: I23488a64faa3a09493a84bf8eae239197e991d7e
2020-12-16 05:27:19 +02:00
Sagi Shnaidman
8255ec4c80 Run images tests
Change-Id: I972bd4d47b92e3a92876c62ab8fd3e8f67be4cd4
2020-12-16 05:22:40 +02:00
Zuul
36ce09a781 Merge "Migrating security_group from AnsibleModule to OpenStackModule" 2020-12-15 16:39:04 +00:00
frenzy_friday
0ac75add62 Migrating network from AnsibleModule to OpenStackModule
1. Created class NetworkModule with run method
2. Changed argument_spec from openstack_full_argument_spec to dict
3. Removed checking min_version for individual parameters and instead used check_versioned method for all together.
4. Since create_network method uses "mtu_size" as a parameter and not "mtu", changed the key "mtu" to "mtu_size" in argument spec and added alias as "mtu" to still support mtu keyword in playbook.
5. Changed "mtu" to "mtu_size" to match in the doc

Change-Id: Ic4ddedb43044434df0a18f8aacacd21149e6f0b0
2020-12-15 15:39:22 +01:00
Zuul
d004e0af05 Merge "Migrating dns_zone from AnsibleModule to OpenStackModule" 2020-12-15 13:13:23 +00:00
Zuul
b040392238 Merge "Migrating routers from AnsibleModule to OpenStackModule" 2020-12-15 12:47:39 +00:00
Kristian Kucerak
19f24568a2 Migrating dns_zone from AnsibleModule to OpenStackModule
Change-Id: Ib8e5a6afe0ce6a7a095ac489ceed8879926ba7f5
2020-12-15 06:56:00 +00:00
frenzy_friday
9783fbb972 Migrating server_volume from AnsibleModule to OpenStackModule
Change-Id: I55cc89b9d043952c9bec6dccf3857a8c7713a7bb
2020-12-15 06:50:34 +00:00
frenzy_friday
b87e474192 Migrating routers from AnsibleModule to OpenStackModule
Change-Id: I6a444c33f2260b79a4f8f75ed5fe73d64fc85c06
2020-12-15 06:50:19 +00:00
frenzy_friday
c1b8786160 Migrating security_group from AnsibleModule to OpenStackModule
Change-Id: I2d861826d0e85f11f4a9d2eefc5a8e63fd1eb72e
2020-12-15 06:49:59 +00:00
Sagi Shnaidman
981d268039 Fix branchful jobs for collections
Stable branch jobs were using master for deployment

Story: #2008445
Task: #41412

Story: #2008444
Task: #41411

Mark train non-voting, see https://review.opendev.org/766622
Change-Id: I8132ec7cfe3468daaa363efb76c5d0b81bdeab30
2020-12-14 17:44:41 +02:00
Zuul
af27a79312 Merge "Migrating volume from AnsibleModule to OpenStackModule" 2020-12-08 16:05:03 +00:00
Sagi Shnaidman
e504d807de Fix docs-args mismatch in modules
Change-Id: I51105f11565c5ff33b04add36259c8703af11240
2020-12-08 12:06:42 +02:00
Vladimir Hasko
faada98ed9 Migrating volume from AnsibleModule to OpenStackModule
A bit restructed structure so decision logic whether volume will be created or updated is now in run method

Change-Id: I83e03787b3cea65f07dc83764743702d59e8656d
2020-12-07 14:22:22 +00:00
Sagi Shnaidman
c914c42799 Improve "server" module with OpenstackModule class
Move all functions that requires OpenstackModule methods to
OpenstackModule class.

Change-Id: I530413cdb6df782556006ff4de78242679f3f5c5
2020-12-03 15:32:14 +02:00
anbanerj
d5c403cded Migrating subnets_info from AnsibleModule to OpenStackModule
Updated module, added "deprecated_names", Removed "ansible-deprecated-no-collection-name" for subnets_info for ansible 2.10, 2.11

Change-Id: I5590976964543188518200f2b31a1603eb30f39b
2020-12-02 13:27:10 +01:00
frenzy_friday
d36ac1f125 Migrating networks_info from AnsibleModule to OpenStackModule
Migrated networks_info module to OpenStackModule and updated playbook to test the module in CI jobs. Added deprecated_names tyo module.
Removed "ansible-deprecated-no-collection-name" exception for networks_info for ansible 2.10 and 2.11. Reverted 'False' to 'false' and updated filters (case sensitive) to check for 'False' instead

Change-Id: I85e19f0db8b4ee549137249477d0b7f5d82e9865
2020-12-01 22:35:03 +00:00
Zuul
15675ce23f Merge "Refactor loadbalancer module" 2020-11-30 20:20:54 +00:00
Zuul
8180fe8af8 Merge "Refactor TCP/UDP port check." 2020-11-30 20:10:59 +00:00
Zuul
88f03fa1df Merge "Add tests for volume_info module" 2020-11-27 00:01:18 +00:00
Zuul
565f7fd369 Merge "Fix subnets update and idempotency" 2020-11-26 19:56:28 +00:00
Sagi Shnaidman
47a0d625dc Add tests for volume_info module
Add tests for volume_info

Change-Id: I8f30eed2a9d5183d0d38a89a7d39e34f7e7c2212
2020-11-26 21:49:32 +02:00
Zuul
b09d8248f7 Merge "Fix volume_info arguments for SDK 0.19" 2020-11-26 18:06:32 +00:00
siavashsardari
bce3eea5c0 Refactor TCP/UDP port check.
Task: 41314
Story: 2008390
Change-Id: Ib479dbef68cede6189d25e75388d8cb1fc61f95f
2020-11-26 17:49:14 +02:00
Sagi Shnaidman
134a8e9d23 Fix subnets update and idempotency
Fix subnet idempotency for allocation pools, see the linked story.
Return updated subnet information.
Remove adding allocation pools that were introduced in
Ib8becf5e958f1bc8e5c9fd76f1722536bf1c9f1a
in order to add allocation pools, either add new variable or
recreate the subnet.

Task: 41307
Story: 2008384

Change-Id: Ibe808227de159c6975dc94ef8ad0ab03a9345e17
2020-11-26 18:29:40 +03:30
Sagi Shnaidman
9ed9b1d399 Fix Ansible devel jobs
Change-Id: I2ce5b1f8cbb673d70a0a2250862009dfeb399d0e
2020-11-24 22:40:09 +02:00
Sagi Shnaidman
80abd782da Fix volume_info arguments for SDK 0.19
all_projects is not supported before openstacksdk 0.19,
use min_ver for using it.

Task: 40865
Story: 2008136
Change-Id: I0f02a47c11122c5b07ca650a830044bca56c3610
2020-11-24 19:48:19 +02:00
Sagi Shnaidman
ee9a5c564e Add victoria stable branch job
Change-Id: I183c9915be6442018dea93b32a4a93bb6df7acae
2020-11-24 13:19:53 +02:00
Zuul
393b484e5a Merge "Move CI jobs to base on Ansible 2.10 release" 2020-11-24 11:06:22 +00:00
Sagi Shnaidman
6117f7062e Move CI jobs to base on Ansible 2.10 release
Change-Id: Ib1884a1a7b69044cf7d0ac9469c677593339eb5c
2020-11-23 21:50:57 +02:00
anbanerj
f89eea10b4 Added deprecated_names for router_info module
Change-Id: I15bc654f1567ebfa4319523be4a9a8f4124898aa
2020-11-23 18:59:20 +01:00
anbanerj
e1178fde34 Migrating routers_info from AnsibleModule to OpenStackModule
Change-Id: I0b87c5c3336849bd2e62da5dee04614f74714dbf
2020-11-19 12:27:27 +02:00
Dmitriy Rabotyagov
8b35c64fda Do not fail when endpoint state is absent
In case endpoint state is absent we shouldn't fail in case service does
not exist, since it means that we're ok, and endpoint is not present.

This might be pretty useful, when user tries to create and delete service
and endpoint with the same code ie [1]

[1] https://opendev.org/openstack/openstack-ansible-tests/src/branch/master/sync/tasks/service_setup.yml

Change-Id: If7ecd7b2e28c81ffe18539731edd4efa599c42ec
Closes-Bug: #1904029
2020-11-16 10:35:41 +02:00
Dmitry Tantsur
8b98452cbb Refactor ironic authentication into a new module_utils module
This change merely moves the code to one location. The next logical
step would be to make IronicModule inherit the common ansible module.

Change-Id: Iec0ca1e33de6ebc36d7664941eafe1d77203d8f2
2020-10-26 11:05:31 +00:00
Zuul
d081bb5378 Merge "OpenStackModule: Support defining a minimum version of the SDK" 2020-10-23 17:11:01 +00:00
Sagi Shnaidman
2ce1adad4a Add galaxy.yml to support install from git
For installing collection from git like:
"ansible-galaxy collection install git+https://..." the galaxy.yml
file is required to be in the collection.
Add galaxy.yml with next version and "-dev".
Fix links for docs and issues.

Change-Id: I74863977732ebea9cd63ccdd2e830a6671a9e955
2020-10-21 09:12:05 +00:00
Mark Chappell
8ca8df1a84 OpenStackModule: Support defining a minimum version of the SDK
While it's currently possible to set min_ver and max_ver for specific
parameter, there are times when the whole module needs to specify a
minimum version:

- When the object isn't supported at all prior to a version
- When major features are missing from the SDK prior to a version

Change-Id: I94bbff7c54621e8a4786ebc7eb030103255dcb17
2020-10-19 13:55:30 +02:00
Riccardo Pittau
058cb4ff3f Migrate bifrost jobs to focal
Change-Id: Ic52dee90bc6b5d5e0ee1bc7fcbfa273867b937e2
2020-10-19 10:43:00 +00:00
Sagi Shnaidman
1c6663999d Add changelog for 1.2.0
Change-Id: I63cff2945703d12d95726a43b33888e80e35b040
2020-10-13 17:00:37 +03:00
Jesper Schmitz Mouridsen
ab96eb6a11 Refactor loadbalancer module
* enable check_mode
 * enable allowed_cidrs on listener if octavia version is >= 2.12
 * Only send flavor_id if it is not None

Change-Id: I4fd36bf3ed347e020151721a6b56d1cac0a8fd23
2020-10-12 19:08:45 +02:00
Zuul
38e61994c7 Merge "Add volume_snapshot_info module" 2020-09-30 16:02:15 +00:00
Artem Goncharov
d416a27112 Add volume_snapshot_info module
Change-Id: I4edc34639f17adb97dd055fcdeec14ea92acb9bd
2020-09-30 13:40:54 +02:00
Zuul
631e1412a0 Merge "Add volume_backup_info module" 2020-09-30 09:52:35 +00:00
Zuul
4c31ea152e Merge "Make it possible to create a health monitor to a pool" 2020-09-30 07:54:37 +00:00
Artem Goncharov
a39470ac2b Add volume_backup_info module
Change-Id: I5ef76247a449b1b8653bb2bb91fccd5f3db57cf8
2020-09-30 07:38:29 +02:00
Artem Goncharov
39a8362d7a Add volume_backup module
Introduce volume_backup module to manage volume/snapshot backups.

Change-Id: Ibc4e87d47d8e38a0cf52e391dafdf025ab202982
2020-09-29 18:59:02 +02:00
Jesper Schmitz Mouridsen
05da83520e Make it possible to create a health monitor to a pool
Change-Id: I6119f5be02ace88253cba448f5a0699b39ea9ee1
2020-09-28 19:37:24 +02:00
Sagi Shnaidman
a6b52612de Fix linters for new ansible release
Limit ansible to <2.10 in 2.9 jobs,
run on ubuntu-bionic because it can provide python 3.6

Change-Id: I6d19842711f3af58449e056bee84a4c5614cd37e
2020-09-24 23:59:26 +03:00
Sagi Shnaidman
a67272d1f5 Add CI files config to bifrost jobs as well
Change-Id: Ife7bd55f44bf709319c6598c0f95201a1aac528f
2020-08-18 10:42:54 +00:00
51 changed files with 2908 additions and 1489 deletions

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
.tox
build_artifact
ansible_collections
galaxy.yml
FILES.json
MANIFEST.json
importer_result.json

View File

@@ -8,11 +8,16 @@
using master of openstacksdk with latest ansible release
required-projects:
- openstack/ansible-collections-openstack
- openstack/designate
vars:
zuul_work_dir: src/opendev.org/openstack/ansible-collections-openstack
tox_envlist: ansible-2.9
tox_envlist: ansible
tox_install_siblings: true
fetch_subunit: false
devstack_plugins:
designate: https://opendev.org/openstack/designate
devstack_services:
designate: true
- job:
name: ansible-collections-openstack-functional-devstack-releases
@@ -21,9 +26,22 @@
Run openstack collections functional tests against a master devstack
using releases of openstacksdk and latest ansible release
vars:
tox_envlist: ansible-2.9
tox_envlist: ansible
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
description: |
Run openstack collections functional tests against a master devstack
using master of openstacksdk and stable 2.9 branch of ansible
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
vars:
tox_envlist: ansible-2.9
- job:
name: ansible-collections-openstack-functional-devstack-ansible-2.10
parent: ansible-collections-openstack-functional-devstack
@@ -36,18 +54,6 @@
vars:
tox_envlist: ansible
- job:
name: ansible-collections-openstack-functional-devstack-ansible-2.9
parent: ansible-collections-openstack-functional-devstack
description: |
Run openstack collections functional tests against a master devstack
using master of openstacksdk and stable 2.9 branch of ansible
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
vars:
tox_envlist: ansible-2.9
- job:
name: ansible-collections-openstack-functional-devstack-ansible-devel
parent: ansible-collections-openstack-functional-devstack
@@ -59,7 +65,7 @@
- name: github.com/ansible/ansible
override-checkout: devel
vars:
tox_envlist: ansible
tox_envlist: ansible-2.11
# Pip installation job
- job:
@@ -72,106 +78,113 @@
vars:
tox_envlist: ansible-pip
# Stable branches
# Stable branches tests
- job:
name: ansible-collections-openstack-functional-devstack-ussuri-ansible-2.9
name: ansible-collections-openstack-functional-devstack-victoria-ansible-2.10
parent: ansible-collections-openstack-functional-devstack-ansible-devel
description: |
Run openstack collections functional tests against a victoria devstack
using victoria brach of openstacksdk and stable 2.10 branch of ansible
voting: true
override-checkout: stable/victoria
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.10
- name: openstack/openstacksdk
override-checkout: stable/victoria
vars:
tox_envlist: ansible
- job:
name: ansible-collections-openstack-functional-devstack-ussuri-ansible-2.10
parent: ansible-collections-openstack-functional-devstack-ansible-devel
description: |
Run openstack collections functional tests against a ussuri devstack
using ussuri brach of openstacksdk and stable 2.9 branch of ansible
using ussuri brach of openstacksdk and stable 2.10 branch of ansible
voting: true
override-checkout: stable/ussuri
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
override-checkout: stable-2.10
- name: openstack/openstacksdk
override-branch: ussuri
- name: openstack/devstack
override-checkout: ussuri
override-checkout: stable/ussuri
- name: openstack/os-client-config
override-checkout: stable/ussuri
vars:
tox_envlist: ansible-2.9
tox_envlist: ansible
- job:
name: ansible-collections-openstack-functional-devstack-train-ansible-2.9
name: ansible-collections-openstack-functional-devstack-train-ansible-2.10
parent: ansible-collections-openstack-functional-devstack-ansible-devel
description: |
Run openstack collections functional tests against a train devstack
using train brach of openstacksdk and stable 2.9 branch of ansible
voting: true
using train brach of openstacksdk and stable 2.10 branch of ansible
voting: false
override-checkout: stable/train
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
override-checkout: stable-2.10
- name: openstack/openstacksdk
override-branch: train
- name: openstack/devstack
override-checkout: train
override-checkout: stable/train
- name: openstack/os-client-config
override-checkout: stable/train
vars:
tox_envlist: ansible-2.9
tox_envlist: ansible
- job:
name: ansible-collections-openstack-functional-devstack-stein-ansible-2.9
name: ansible-collections-openstack-functional-devstack-stein-ansible-2.10
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.9 branch of ansible
using stein brach of openstacksdk and stable 2.10 branch of ansible
voting: true
override-checkout: stable/stein
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
override-checkout: stable-2.10
- name: openstack/openstacksdk
override-branch: stein
- name: openstack/devstack
override-checkout: stein
override-checkout: stable/stein
- name: openstack/os-client-config
override-checkout: stable/stein
vars:
tox_envlist: ansible-2.9
tox_envlist: ansible
- job:
name: ansible-collections-openstack-functional-devstack-rocky-ansible-2.9
name: ansible-collections-openstack-functional-devstack-rocky-ansible-2.10
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.9 branch of ansible
using rocky brach of openstacksdk and stable 2.10 branch of ansible
voting: true
override-checkout: stable/rocky
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
override-checkout: stable-2.10
- name: openstack/openstacksdk
override-branch: rocky
- name: openstack/devstack
override-checkout: rocky
vars:
tox_envlist: ansible-2.9
override-checkout: stable/rocky
- name: openstack/os-client-config
override-checkout: stable/rocky
- name: openstack/shade
override-checkout: stable/rocky
- job:
name: ansible-collections-openstack-functional-devstack-queens-ansible-2.9
parent: ansible-collections-openstack-functional-devstack-ansible-devel
description: |
Run openstack collections functional tests against a queens devstack
using master brach of openstacksdk and stable 2.9 branch of ansible
voting: true
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
- name: openstack/openstacksdk
override-branch: master
- name: openstack/devstack
override-checkout: queens
vars:
tox_envlist: ansible-2.9
tox_envlist: ansible
- job:
name: ansible-collections-openstack-functional-devstack-queens-ansible-2.10
parent: ansible-collections-openstack-functional-devstack-ansible-devel
description: |
Run openstack collections functional tests against a queens devstack
using master brach of openstacksdk and stable 2.10 branch of ansible
using master branch of openstacksdk and stable 2.10 branch of ansible
voting: true
override-checkout: stable/queens
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.10
- name: openstack/openstacksdk
override-branch: master
- name: openstack/devstack
override-checkout: queens
# Run queens with highest possible py2 version of SDK
override-checkout: stable/train
vars:
tox_envlist: ansible
@@ -180,23 +193,24 @@
parent: ansible-collections-openstack-functional-devstack-ansible-devel
description: |
Run openstack collections functional tests against a queens devstack
using master brach of openstacksdk and devel branch of ansible
voting: true
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
- name: openstack/openstacksdk
override-branch: master
- name: openstack/devstack
override-checkout: queens
# Run queens with highest possible py2 version of SDK
override-checkout: stable/train
vars:
tox_envlist: ansible
tox_envlist: ansible-2.11
# Linters
- job:
name: openstack-tox-linters-ansible-devel
parent: openstack-tox-linters
nodeset: ubuntu-bionic
description: |
Run openstack collections linter tests using the devel branch of ansible
# non-voting because we can't prevent ansible devel from breaking us
@@ -204,10 +218,13 @@
required-projects:
- name: github.com/ansible/ansible
override-checkout: devel
vars:
tox_envlist: linters-2.11
- job:
name: openstack-tox-linters-ansible-2.10
parent: openstack-tox-linters
nodeset: ubuntu-bionic
description: |
Run openstack collections linter tests using the 2.10 branch of ansible
voting: true
@@ -218,6 +235,7 @@
- job:
name: openstack-tox-linters-ansible-2.9
parent: openstack-tox-linters
nodeset: ubuntu-bionic
description: |
Run openstack collections linter tests using the 2.9 branch of ansible
voting: true
@@ -230,13 +248,13 @@
# Cross-checks with other projects
- job:
name: bifrost-collections-src
parent: bifrost-integration-tinyipa-ubuntu-bionic
parent: bifrost-integration-tinyipa-ubuntu-focal
required-projects:
- openstack/ansible-collections-openstack
- job:
name: bifrost-keystone-collections-src
parent: bifrost-integration-tinyipa-keystone-ubuntu-bionic
parent: bifrost-integration-tinyipa-keystone-ubuntu-focal
required-projects:
- openstack/ansible-collections-openstack
@@ -251,6 +269,7 @@
dependencies: &deps_unit_lint
- tox-pep8
- openstack-tox-linters-ansible-2.9
- openstack-tox-linters-ansible-2.10
irrelevant-files: &ignore_files
- changelogs/.*
- COPYING
@@ -261,9 +280,6 @@
- tests/sanity/.*
- contrib/.*
- ansible-collections-openstack-functional-devstack:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- ansible-collections-openstack-functional-devstack-releases:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
@@ -279,26 +295,33 @@
- ansible-collections-openstack-functional-devstack-ansible-pip:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.9:
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.10:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- ansible-collections-openstack-functional-devstack-train-ansible-2.9:
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.10:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- ansible-collections-openstack-functional-devstack-stein-ansible-2.9:
- ansible-collections-openstack-functional-devstack-train-ansible-2.10:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- ansible-collections-openstack-functional-devstack-rocky-ansible-2.9:
- ansible-collections-openstack-functional-devstack-stein-ansible-2.10:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- ansible-collections-openstack-functional-devstack-queens-ansible-2.9:
- ansible-collections-openstack-functional-devstack-rocky-ansible-2.10:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- ansible-collections-openstack-functional-devstack-queens-ansible-2.10:
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- bifrost-collections-src:
voting: false
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
- bifrost-keystone-collections-src:
voting: false
dependencies: *deps_unit_lint
irrelevant-files: *ignore_files
gate:
jobs:
@@ -310,11 +333,12 @@
- ansible-collections-openstack-functional-devstack-ansible-2.9
- ansible-collections-openstack-functional-devstack-ansible-2.10
- ansible-collections-openstack-functional-devstack-ansible-pip
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.9
- ansible-collections-openstack-functional-devstack-train-ansible-2.9
- ansible-collections-openstack-functional-devstack-stein-ansible-2.9
- ansible-collections-openstack-functional-devstack-rocky-ansible-2.9
- ansible-collections-openstack-functional-devstack-queens-ansible-2.9
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.10
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.10
# - ansible-collections-openstack-functional-devstack-train-ansible-2.10
- ansible-collections-openstack-functional-devstack-stein-ansible-2.10
- ansible-collections-openstack-functional-devstack-rocky-ansible-2.10
- ansible-collections-openstack-functional-devstack-queens-ansible-2.10
periodic:
jobs:
@@ -327,11 +351,11 @@
- ansible-collections-openstack-functional-devstack-ansible-2.10
- ansible-collections-openstack-functional-devstack-ansible-devel
- ansible-collections-openstack-functional-devstack-ansible-pip
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.9
- ansible-collections-openstack-functional-devstack-train-ansible-2.9
- ansible-collections-openstack-functional-devstack-stein-ansible-2.9
- ansible-collections-openstack-functional-devstack-rocky-ansible-2.9
- ansible-collections-openstack-functional-devstack-queens-ansible-2.9
- ansible-collections-openstack-functional-devstack-victoria-ansible-2.10
- ansible-collections-openstack-functional-devstack-ussuri-ansible-2.10
- ansible-collections-openstack-functional-devstack-train-ansible-2.10
- ansible-collections-openstack-functional-devstack-stein-ansible-2.10
- ansible-collections-openstack-functional-devstack-rocky-ansible-2.10
- ansible-collections-openstack-functional-devstack-queens-ansible-2.10
- ansible-collections-openstack-functional-devstack-queens-ansible-devel
- bifrost-collections-src

View File

@@ -5,6 +5,60 @@ Openstack Cloud Ansilbe modules Release Notes
.. contents:: Topics
v1.2.1
======
Release Summary
---------------
Porting modules to new OpenstackModule class and fixes.
Minor Changes
-------------
- dns_zone - Migrating dns_zone from AnsibleModule to OpenStackModule
- dns_zone, recordset - Enable update for recordset and add tests for dns and recordset module
- endpoint - Do not fail when endpoint state is absent
- ironic - Refactor ironic authentication into a new module_utils module
- loadbalancer - Refactor loadbalancer module
- network - Migrating network from AnsibleModule to OpenStackModule
- networks_info - Migrating networks_info from AnsibleModule to OpenStackModule
- openstack - Add galaxy.yml to support install from git
- openstack - Fix docs-args mismatch in modules
- openstack - OpenStackModule Support defining a minimum version of the SDK
- router - Migrating routers from AnsibleModule to OpenStackModule
- routers_info - Added deprecated_names for router_info module
- routers_info - Migrating routers_info from AnsibleModule to OpenStackModule
- security_group.py - Migrating security_group from AnsibleModule to OpenStackModule
- security_group_rule - Refactor TCP/UDP port check
- server.py - Improve "server" module with OpenstackModule class
- server_volume - Migrating server_volume from AnsibleModule to OpenStackModule
- subnet - Fix subnets update and idempotency
- subnet - Migrating subnet module from AnsibleModule to OpenStackModule
- subnets_info - Migrating subnets_info from AnsibleModule to OpenStackModule
- volume.py - Migrating volume from AnsibleModule to OpenStackModule
- volume_info - Fix volume_info arguments for SDK 0.19
v1.2.0
======
Release Summary
---------------
New volume backup modules.
Minor Changes
-------------
- lb_health_monitor - Make it possible to create a health monitor to a pool
New Modules
-----------
- openstack.cloud.volume_backup module - Add/Delete Openstack volumes backup.
- openstack.cloud.volume_backup_info module - Retrieve information about Openstack volume backups.
- openstack.cloud.volume_snapshot_info module - Retrieve information about Openstack volume snapshots.
v1.1.0
======
@@ -24,12 +78,6 @@ Minor Changes
- inventory_openstack - Add openstack logger and Ansible display utility
- loadbalancer - Add support for setting the Flavor when creating a load balancer
Deprecated Features
-------------------
- foo - The bar option has been deprecated. Use the username option instead.
- send_request - The quic option has been deprecated. Use the protocol option instead.
Bugfixes
--------

View File

@@ -1,20 +1,59 @@
releases:
1.2.1:
release_date: '2021-01-03'
changes:
release_summary: Porting modules to new OpenstackModule class and fixes.
minor_changes:
- dns_zone, recordset - Enable update for recordset and add tests for dns and recordset module
- loadbalancer - Refactor loadbalancer module
- openstack - OpenStackModule Support defining a minimum version of the SDK
- ironic - Refactor ironic authentication into a new module_utils module
- endpoint - Do not fail when endpoint state is absent
- subnet - Fix subnets update and idempotency
- volume_info - Fix volume_info arguments for SDK 0.19
- routers_info - Added deprecated_names for router_info module
- security_group_rule - Refactor TCP/UDP port check
- openstack - Fix docs-args mismatch in modules
- security_group.py - Migrating security_group from AnsibleModule to OpenStackModule
- router - Migrating routers from AnsibleModule to OpenStackModule
- server_volume - Migrating server_volume from AnsibleModule to OpenStackModule
- dns_zone - Migrating dns_zone from AnsibleModule to OpenStackModule
- network - Migrating network from AnsibleModule to OpenStackModule
- routers_info - Migrating routers_info from AnsibleModule to OpenStackModule
- networks_info - Migrating networks_info from AnsibleModule to OpenStackModule
- subnets_info - Migrating subnets_info from AnsibleModule to OpenStackModule
- server.py - Improve "server" module with OpenstackModule class
- volume.py - Migrating volume from AnsibleModule to OpenStackModule
- subnet - Migrating subnet module from AnsibleModule to OpenStackModule
- openstack - Add galaxy.yml to support install from git
1.2.0:
release_date: '2020-10-13'
changes:
release_summary: New volume backup modules.
minor_changes:
- lb_health_monitor - Make it possible to create a health monitor to a pool
modules:
- name: volume_snapshot_info module
description: Retrieve information about Openstack volume snapshots.
namespace: ''
- name: volume_backup_info module
description: Retrieve information about Openstack volume backups.
namespace: ''
- name: volume_backup module
description: Add/Delete Openstack volumes backup.
namespace: ''
1.1.0:
release_date: '2020-08-17'
changes:
release_summary: Starting redesign modules and bugfixes.
minor_changes:
- Added changelog.
- A basic module subclass was introduced and a few modules moved to inherit from it.
- Added pip installation option for collection.
- Added template for generation of artibtrary module.
- Added more useful information from exception
- Add more useful information from exception
- inventory_openstack - Add openstack logger and Ansible display utility
- loadbalancer - Add support for setting the Flavor when creating a load balancer
- baremetal modules - Do not require ironic_url if cloud or auth.endpoint is provided
deprecated_features:
- foo - The bar option has been deprecated. Use the username option instead.
- send_request - The quic option has been deprecated. Use the protocol option instead.
bugfixes:
- security_group_rule - Don't pass tenant_id for remote group
- Fix non existing attribuites in SDK exception

View File

@@ -0,0 +1,4 @@
dns_zone_name: test.dns.zone.
recordset_name: testrecordset.test.dns.zone.
records: ['10.0.0.0']
updated_records: ['10.1.1.1']

View File

@@ -0,0 +1,79 @@
---
- name: Create dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ dns_zone_name }}"
zone_type: "primary"
email: test@example.net
register: dns_zone
- debug: var=dns_zone
- name: Update dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ dns_zone.zone.name }}"
description: "New descirption"
register: updated_dns_zone
- debug: var=updated_dns_zone
- name: Create a recordset
openstack.cloud.recordset:
cloud: "{{ cloud }}"
zone: "{{ updated_dns_zone.zone.name }}"
name: "{{ recordset_name }}"
recordset_type: "a"
records: "{{ records }}"
register: recordset
- name: Verify recordset info
assert:
that:
- recordset["recordset"].name == recordset_name
- recordset["recordset"].zone_name == dns_zone.zone.name
- recordset["recordset"].records == records
- name: Update a recordset
openstack.cloud.recordset:
cloud: "{{ cloud }}"
zone: "{{ updated_dns_zone.zone.name }}"
name: "{{ recordset_name }}"
recordset_type: "a"
records: "{{ updated_records }}"
description: "new test recordset"
register: updated_recordset
- name: Verify recordset info
assert:
that:
- updated_recordset["recordset"].zone_name == dns_zone.zone.name
- updated_recordset["recordset"].name == recordset_name
- updated_recordset["recordset"].records == updated_records
- name: Delete recordset
openstack.cloud.recordset:
cloud: "{{ cloud }}"
zone: "{{ updated_dns_zone.zone.name }}"
name: "{{ recordset.recordset.name }}"
state: absent
register: deleted_recordset
- name: Verify recordset deletion
assert:
that:
- deleted_recordset is successful
- deleted_recordset is changed
- name: Delete dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ updated_dns_zone.zone.name }}"
state: absent
register: deleted_dns_zone
- name: Verify dns zone
assert:
that:
- deleted_dns_zone is successful
- deleted_dns_zone is changed

View File

@@ -1,3 +1,7 @@
network_name: shade_network
network_name_newparams: newparams_network
network_shared: false
network_external: false
dns_domain: example.opendev.org
mtu: 1250
port_security_enabled: false

View File

@@ -1,5 +1,5 @@
---
- name: Create network
- name: Create network - generic
openstack.cloud.network:
cloud: "{{ cloud }}"
name: "{{ network_name }}"
@@ -7,8 +7,71 @@
shared: "{{ network_shared }}"
external: "{{ network_external }}"
- name: Delete network
openstack.cloud.network:
- name: Gather networks info - generic
openstack.cloud.networks_info:
cloud: "{{ cloud }}"
name: "{{ network_name }}"
filters:
shared: "{{ network_shared|string|capitalize }}"
register: result
- name: Verify networks info - generic
assert:
that:
- result.openstack_networks.0.name == network_name
- (result.openstack_networks.0.shared|lower) == (network_shared|lower)
- result.openstack_networks[0]['router:external'] == {{ network_external }}
- name: Create network - with new SDK params
openstack.cloud.network:
cloud: "{{ cloud }}"
name: "{{ network_name_newparams }}"
state: present
shared: "{{ network_shared }}"
external: "{{ network_external }}"
mtu: "{{ mtu }}"
port_security_enabled: "{{ port_security_enabled }}"
register: result_create_nw_with_new_params
ignore_errors: yes
- name: Check errors below min sdk version - with new SDK params
assert:
that:
- result_create_nw_with_new_params.failed
- '"the installed version of the openstacksdk library MUST be >=0.18.0." in result_create_nw_with_new_params.msg'
when: sdk_version is version('0.18', '<')
- name: Gather networks info - with new SDK params
openstack.cloud.networks_info:
cloud: "{{ cloud }}"
name: "{{ network_name_newparams }}"
register: result_newparams
when: sdk_version is version('0.18', '>=')
- name: Verify networks info - with new SDK params
assert:
that:
- result_newparams.openstack_networks.0.name == network_name_newparams
- result_newparams.openstack_networks.0.mtu == mtu
- result_newparams.openstack_networks.0.port_security_enabled == port_security_enabled
when: sdk_version is version('0.18', '>=')
- name: Delete network - generic and with new SDK params
openstack.cloud.network:
cloud: "{{ cloud }}"
name: "{{ item }}"
state: absent
with_items:
- "{{ network_name }}"
- "{{ network_name_newparams }}"
- name: Gather networks info - deleted
openstack.cloud.networks_info:
cloud: "{{ cloud }}"
name: "{{ network_name }}"
register: result_nonet
- name: Verify networks info - deleted
assert:
that:
- result_nonet.openstack_networks == []

View File

@@ -32,6 +32,26 @@
protocol: tcp
remote_ip_prefix: 0.0.0.0/0
- name: Create TCP rule again with port range (1, 65535)
openstack.cloud.security_group_rule:
cloud: "{{ cloud }}"
security_group: "{{ secgroup_name }}"
state: present
protocol: tcp
port_range_min: 1
port_range_max: 65535
remote_ip_prefix: 0.0.0.0/0
- name: Create TCP rule again with port range (-1, -1)
openstack.cloud.security_group_rule:
cloud: "{{ cloud }}"
security_group: "{{ secgroup_name }}"
state: present
protocol: tcp
port_range_min: -1
port_range_max: -1
remote_ip_prefix: 0.0.0.0/0
- name: Create empty UDP rule
openstack.cloud.security_group_rule:
cloud: "{{ cloud }}"
@@ -40,6 +60,26 @@
protocol: udp
remote_ip_prefix: 0.0.0.0/0
- name: Create UDP rule again with port range (1, 65535)
openstack.cloud.security_group_rule:
cloud: "{{ cloud }}"
security_group: "{{ secgroup_name }}"
state: present
protocol: udp
port_range_min: 1
port_range_max: 65535
remote_ip_prefix: 0.0.0.0/0
- name: Create UDP rule again with port range (-1, -1)
openstack.cloud.security_group_rule:
cloud: "{{ cloud }}"
security_group: "{{ secgroup_name }}"
state: present
protocol: udp
port_range_min: -1
port_range_max: -1
remote_ip_prefix: 0.0.0.0/0
- name: Create HTTP rule
openstack.cloud.security_group_rule:
cloud: "{{ cloud }}"

View File

@@ -124,6 +124,33 @@
terminate_volume: true
wait: true
register: server
tags:
- object
- debug: var=server
tags:
- object
- name: Delete server with volume
openstack.cloud.server:
cloud: "{{ cloud }}"
state: absent
name: "{{ server_name }}"
wait: true
tags:
- object
- name: Create a minimal server
openstack.cloud.server:
cloud: "{{ cloud }}"
state: present
name: "{{ server_name }}"
image: "{{ image }}"
flavor: "{{ flavor }}"
network: "{{ server_network }}"
auto_floating_ip: false
wait: true
register: server
- debug: var=server
@@ -150,7 +177,7 @@
that:
info.openstack_servers|length > 0
- name: Delete server with volume
- name: Delete minimal server
openstack.cloud.server:
cloud: "{{ cloud }}"
state: absent

View File

@@ -200,7 +200,10 @@
that:
- info10.openstack_servers.0.status == 'ACTIVE'
# not in all versions 'locked' is supported
- info10.openstack_servers.0.locked in (None, True)
- >-
(info10.openstack_server[0]['locked'] is defined and
info10.openstack_server[0]['locked']|bool) or
(info10.openstack_server[0]['locked'] is not defined)
- server is changed
- name: Lock server again
@@ -222,7 +225,10 @@
that:
- info11.openstack_servers.0.status == 'ACTIVE'
# not in all versions 'locked' is supported
- info11.openstack_servers.0.locked in (None, True)
- >-
(info11.openstack_server[0]['locked'] is defined and
info11.openstack_server[0]['locked']|bool) or
(info11.openstack_server[0]['locked'] is not defined)
- server is changed # no support for lock idempotency
- name: Unock server
@@ -244,7 +250,10 @@
that:
- info12.openstack_servers.0.status == 'ACTIVE'
# not in all versions 'locked' is supported
- info12.openstack_servers.0.locked in (None, False)
- >-
(info12.openstack_server[0]['locked'] is defined and
not info12.openstack_server[0]['locked']|bool) or
(info12.openstack_server[0]['locked'] is not defined)
- server is changed
- name: Unlock server again
@@ -267,7 +276,10 @@
- info13.openstack_servers.0.status == 'ACTIVE'
- server is changed # no support for unlock idempotency
# not in all versions 'locked' is supported
- info13.openstack_servers.0.locked in (None, False)
- >-
(info13.openstack_server[0]['locked'] is defined and
not info13.openstack_server[0]['locked']|bool) or
(info13.openstack_server[0]['locked'] is not defined)
- name: Suspend server
openstack.cloud.server_action:

View File

@@ -17,6 +17,19 @@
allocation_pool_start: 192.168.0.2
allocation_pool_end: 192.168.0.4
- name: Create subnet {{ subnet_name }} on network {{ network_name }} again
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
enable_dhcp: "{{ enable_subnet_dhcp }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.0.0/24
gateway_ip: 192.168.0.1
allocation_pool_start: 192.168.0.2
allocation_pool_end: 192.168.0.4
register: idem1
- name: Update subnet {{ subnet_name }} allocation pools
openstack.cloud.subnet:
cloud: "{{ cloud }}"
@@ -24,7 +37,7 @@
name: "{{ subnet_name }}"
state: present
cidr: 192.168.0.0/24
allocation_pool_start: 192.168.0.5
allocation_pool_start: 192.168.0.2
allocation_pool_end: 192.168.0.8
- name: Get Subnet Info
@@ -36,19 +49,17 @@
- name: Verify Subnet Allocation Pools Exist
assert:
that:
- idem1 is not changed
- subnet_result.openstack_subnets is defined
- subnet_result.openstack_subnets | length == 1
- subnet_result.openstack_subnets[0].allocation_pools is defined
- subnet_result.openstack_subnets[0].allocation_pools | length == 2
- subnet_result.openstack_subnets[0].allocation_pools | length == 1
- name: Verify Subnet Allocation Pools
assert:
that:
- subnet_result.openstack_subnets[0].allocation_pools | selectattr('start','equalto',item.start) | list | count > 0
- subnet_result.openstack_subnets[0].allocation_pools | selectattr('end','equalto',item.end) | list | count > 0
loop:
- {start: '192.168.0.2', end: '192.168.0.4'}
- {start: '192.168.0.5', end: '192.168.0.8'}
- subnet_result.openstack_subnets[0].allocation_pools.0.start == '192.168.0.2'
- subnet_result.openstack_subnets[0].allocation_pools.0.end == '192.168.0.8'
- name: Delete subnet {{ subnet_name }}
openstack.cloud.subnet:

View File

@@ -8,10 +8,64 @@
display_description: Test volume
register: vol
- name: Create volume snapshot
openstack.cloud.volume_snapshot:
cloud: "{{ cloud }}"
state: present
display_name: ansible_volume_snapshot
volume: ansible_volume
register: vol_snap
- name: Get snapshot info
openstack.cloud.volume_snapshot_info:
cloud: "{{ cloud }}"
name: ansible_volume_snapshot
register: snap_info
ignore_errors: sdk_version is version(0.49, '<')
- name: Create volume backup
openstack.cloud.volume_backup:
cloud: "{{ cloud }}"
state: present
display_name: ansible_volume_backup
volume: ansible_volume
register: vol_backup
ignore_errors: sdk_version is version(0.49, '<')
- name: Get backup info
openstack.cloud.volume_backup_info:
cloud: "{{ cloud }}"
name: ansible_volume_backup
register: backup_info
ignore_errors: sdk_version is version(0.49, '<')
- debug: var=vol
- debug: var=vol_backup
- debug: var=backup_info
- debug: var=snap_info
- name: Delete volume backup
openstack.cloud.volume_backup:
cloud: "{{ cloud }}"
display_name: ansible_volume_backup
state: absent
ignore_errors: sdk_version is version(0.49, '<')
- name: Delete volume snapshot
openstack.cloud.volume_snapshot:
cloud: "{{ cloud }}"
display_name: ansible_volume_snapshot
volume: ansible_volume
state: absent
- name: Delete volume
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: absent
display_name: ansible_volume
- include_tasks: volume_info.yml

View File

@@ -0,0 +1,155 @@
- name: Get info about volumes and all projects for all SDK
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: true
all_projects: true
register: all_sdk
ignore_errors: true
- name: Check info for all projects
assert:
that:
# Rocky SDK doesn't have all_projects attribute
- >-
(all_sdk is failed and sdk_version is version(0.19, '<')) or
all_sdk is success
- name: Get info about volumes for all SDK
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: true
register: all_sdk1
ignore_errors: true
- name: Check info for all SDK
assert:
that:
- all_sdk1 is success
- all_sdk1.volumes is defined
- name: Run tests for SDK > 0.28 (from train)
when: sdk_version is version(0.28, '>')
block:
- name: Get info about volumes
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
all_projects: true
register: delete
- name: Clean up volumes before the test
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: absent
display_name: "{{ vol.name }}"
loop: "{{ delete.volumes }}"
loop_control:
loop_var: vol
- name: Create volume
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: present
size: 1
display_name: ansible_test
display_description: testci
register: vol
- name: Get info about volumes
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: true
all_projects: true
register: info
- name: Check info
assert:
that:
- info.volumes | selectattr("description", "equalto", "testci") | list | length == 1
- info.volumes.0.name == 'ansible_test'
- info.volumes.0.status == 'available'
- name: Get not detailed info about volumes
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: false
all_projects: true
register: info1
- name: Check info
assert:
that:
- info1.volumes | selectattr("id", "equalto", "{{ info.volumes.0.id }}") | list | length == 1
- info1.volumes.0.name == 'ansible_test'
- info1.volumes.0.status == None
- name: Get info about volumes with name
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: false
name: ansible_test
all_projects: true
register: info2
- name: Check info
assert:
that:
- info2.volumes | length == 1
- info2.volumes.0.name == 'ansible_test'
- name: Get info about volumes with non-existent name
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: false
name: nothing_here
all_projects: true
register: info3
- name: Check info
assert:
that:
- info3.volumes | length == 0
- name: Get info about volumes
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: false
name: ansible_test
all_projects: true
register: info4
- name: Check info
assert:
that:
- info4.volumes | length == 1
- info4.volumes.0.name == 'ansible_test'
- name: Get info about volumes not from all projects
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
details: false
name: ansible_test
register: info4a
- name: Check info
assert:
that:
- info4a.volumes | length == 1
- info4a.volumes.0.name == 'ansible_test'
- name: Delete volume
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: absent
display_name: ansible_test
- name: Get info when no volumes
openstack.cloud.volume_info:
cloud: "{{ cloud }}"
all_projects: true
register: info5
- name: Check info
assert:
that:
- info5.volumes | selectattr("name", "equalto", "ansible_test") | list | length == 0

View File

@@ -7,9 +7,10 @@
- { role: auth, tags: auth }
- { role: client_config, tags: client_config }
- { role: group, tags: group }
# TODO(mordred) Reenable this once the fixed openstack.cloud.image winds up in an
# upstream ansible release.
# - { role: image, tags: image }
- role: dns
tags: dns
when: sdk_version is version(0.28, '>=')
- { role: image, tags: image }
- { role: keypair, tags: keypair }
- { role: keystone_domain, tags: keystone_domain }
- role: keystone_mapping

36
galaxy.yml Normal file
View File

@@ -0,0 +1,36 @@
namespace: openstack
name: cloud
readme: README.md
authors: Openstack
description: Openstack Ansible modules
license: GPL-3.0-or-later
tags:
- cloud
- openstack
dependencies: {}
repository: https://opendev.org/openstack/ansible-collections-openstack
documentation: https://docs.ansible.com/ansible/latest/collections/openstack/cloud/index.html
homepage: https://opendev.org/openstack/ansible-collections-openstack
issues: https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack
build_ignore:
- "*.tar.gz"
- build_artifact
- ci
- galaxy.yml.in
- setup.cfg
- test-requirements.txt
- tests
- tools
- tox.ini
- .gitignore
- .gitreview
- .zuul.yaml
- .pytest_cache
- importer_result.json
- .tox
- .env
- ansible_collections_openstack.egg-info
- contrib
- changelogs/.plugin-cache.yaml
- changelogs/fragments
version: 1.2.1

View File

@@ -8,10 +8,10 @@ tags:
- cloud
- openstack
dependencies: {}
repository: https://opendev.org/openstack/ansible-collections-openstack.git
documentation: https://docs.openstack.org/ansible-collections-openstack
homepage: https://opendev.org
issues: https://review.opendev.org/q/project:openstack/ansible-collections-openstack
repository: https://opendev.org/openstack/ansible-collections-openstack
documentation: https://docs.ansible.com/ansible/latest/collections/openstack/cloud/index.html
homepage: https://opendev.org/openstack/ansible-collections-openstack
issues: https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack
build_ignore:
- "*.tar.gz"
- build_artifact
@@ -27,3 +27,9 @@ build_ignore:
- .zuul.yaml
- .pytest_cache
- importer_result.json
- .tox
- .env
- ansible_collections_openstack.egg-info
- contrib
- changelogs/.plugin-cache.yaml
- changelogs/fragments

View File

@@ -88,8 +88,11 @@ action_groups:
- subnet
- subnets_info
- volume
- volume_backup
- volume_backup_info
- volume_info
- volume_snapshot
- volume_snapshot_info
os:
- auth
- baremetal_inspect
@@ -179,8 +182,11 @@ action_groups:
- subnet
- subnets_info
- volume
- volume_backup
- volume_backup_info
- volume_info
- volume_snapshot
- volume_snapshot_info
- os_auth
- os_client_config
- os_client_config

View File

@@ -61,7 +61,7 @@ options:
- Whether or not SSL API requests should be verified.
- Before Ansible 2.3 this defaulted to C(yes).
type: bool
default: no
default: False
aliases: [ verify ]
ca_cert:
description:

View File

@@ -0,0 +1,68 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec
def ironic_argument_spec(**kwargs):
spec = dict(
auth_type=dict(required=False),
ironic_url=dict(required=False),
)
spec.update(kwargs)
return openstack_full_argument_spec(**spec)
# TODO(dtantsur): inherit the collection's base module
class IronicModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._update_ironic_auth()
def _update_ironic_auth(self):
"""Validate and update authentication parameters for ironic."""
if (
self.params['auth_type'] in [None, 'None', 'none']
and self.params['ironic_url'] is None
and not self.params['cloud']
and not (self.params['auth']
and self.params['auth'].get('endpoint'))
):
self.fail_json(msg=("Authentication appears to be disabled, "
"Please define either ironic_url, or cloud, "
"or auth.endpoint"))
if (
self.params['ironic_url']
and self.params['auth_type'] in [None, 'None', 'none']
and not (self.params['auth']
and self.params['auth'].get('endpoint'))
):
self.params['auth'] = dict(
endpoint=self.params['ironic_url']
)

View File

@@ -67,6 +67,8 @@ OVERRIDES = {'os_client_config': 'config',
CUSTOM_VAR_PARAMS = ['min_ver', 'max_ver']
MINIMUM_SDK_VERSION = '0.12.0'
def openstack_argument_spec():
# DEPRECATED: This argument spec is only used for the deprecated old
@@ -118,7 +120,7 @@ def openstack_full_argument_spec(**kwargs):
auth=dict(default=None, type='dict', no_log=True),
region_name=dict(default=None),
availability_zone=dict(default=None),
validate_certs=dict(default=None, type='bool', aliases=['verify']),
validate_certs=dict(default=False, type='bool', aliases=['verify']),
ca_cert=dict(default=None, aliases=['cacert']),
client_cert=dict(default=None, aliases=['cert']),
client_key=dict(default=None, no_log=True, aliases=['key']),
@@ -150,7 +152,7 @@ def openstack_module_kwargs(**kwargs):
# for compatibility with old versions
def openstack_cloud_from_module(module, min_version='0.12.0'):
def openstack_cloud_from_module(module, min_version=None):
try:
# Due to the name shadowing we should import other way
sdk = importlib.import_module('openstack')
@@ -159,9 +161,10 @@ def openstack_cloud_from_module(module, min_version='0.12.0'):
module.fail_json(msg='openstacksdk is required for this module')
if min_version:
min_version = max(StrictVersion('0.12.0'), StrictVersion(min_version))
min_version = max(StrictVersion(MINIMUM_SDK_VERSION),
StrictVersion(min_version))
else:
min_version = StrictVersion('0.12.0')
min_version = StrictVersion(MINIMUM_SDK_VERSION)
if StrictVersion(sdk_version.__version__) < min_version:
module.fail_json(
@@ -240,6 +243,7 @@ class OpenStackModule:
deprecated_names = ()
argument_spec = {}
module_kwargs = {}
module_min_sdk_version = None
def __init__(self):
"""Initialize Openstack base class.
@@ -300,6 +304,19 @@ class OpenStackModule:
except ImportError:
self.fail_json(msg='openstacksdk is required for this module')
# Fail if the available SDK version doesn't meet the minimum version
# requirements
if self.module_min_sdk_version:
min_version = max(StrictVersion(MINIMUM_SDK_VERSION),
StrictVersion(self.module_min_sdk_version))
else:
min_version = StrictVersion(MINIMUM_SDK_VERSION)
if StrictVersion(self.sdk_version) < min_version:
self.fail(
msg="To utilize this module, the installed version of "
"the openstacksdk library MUST be >={min_version}.".format(
min_version=min_version))
# Fail if there are set unsupported for this version parameters
# New parameters should NOT use 'default' but rely on SDK defaults
for param in self.argument_spec:

View File

@@ -75,10 +75,14 @@ EXAMPLES = '''
name: "testnode1"
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
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 _choose_id_value(module):
@@ -90,37 +94,14 @@ def _choose_id_value(module):
def main():
argument_spec = openstack_full_argument_spec(
auth_type=dict(required=False),
argument_spec = ironic_argument_spec(
uuid=dict(required=False),
name=dict(required=False),
mac=dict(required=False),
ironic_url=dict(required=False),
timeout=dict(default=1200, type='int', required=False),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
if (
module.params['auth_type'] in [None, 'None', 'none']
and module.params['ironic_url'] is None
and not module.params['cloud']
and not (module.params['auth']
and module.params['auth'].get('endpoint'))
):
module.fail_json(msg="Authentication appears to be disabled, "
"Please define either ironic_url, or cloud, "
"or auth.endpoint")
if (
module.params['ironic_url']
and module.params['auth_type'] in [None, 'None', 'none']
and not (module.params['auth']
and module.params['auth'].get('endpoint'))
):
module.params['auth'] = dict(
endpoint=module.params['ironic_url']
)
module = IronicModule(argument_spec, **module_kwargs)
sdk, cloud = openstack_cloud_from_module(module)
try:

View File

@@ -120,7 +120,6 @@ options:
re-assert the password field.
- C(skip_update_of_driver_password) is deprecated alias and will be removed in openstack.cloud 2.0.0.
type: bool
default: 'no'
aliases:
- skip_update_of_driver_password
requirements:
@@ -164,10 +163,15 @@ try:
except ImportError:
HAS_JSONPATCH = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
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 _parse_properties(module):
@@ -225,14 +229,13 @@ def _exit_node_not_updated(module, server):
def main():
argument_spec = openstack_full_argument_spec(
argument_spec = ironic_argument_spec(
uuid=dict(required=False),
name=dict(required=False),
driver=dict(required=False),
driver_info=dict(type='dict', required=True),
nics=dict(type='list', required=True, elements="dict"),
properties=dict(type='dict', default={}),
ironic_url=dict(required=False),
chassis_uuid=dict(required=False),
skip_update_of_masked_password=dict(
required=False,
@@ -243,30 +246,10 @@ def main():
state=dict(required=False, default='present', choices=['present', 'absent'])
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
module = IronicModule(argument_spec, **module_kwargs)
if not HAS_JSONPATCH:
module.fail_json(msg='jsonpatch is required for this module')
if (
module.params['auth_type'] in [None, 'None', 'none']
and module.params['ironic_url'] is None
and not module.params['cloud']
and not (module.params['auth']
and module.params['auth'].get('endpoint'))
):
module.fail_json(msg="Authentication appears to be disabled, "
"Please define either ironic_url, or cloud, "
"or auth.endpoint")
if (
module.params['ironic_url']
and module.params['auth_type'] in [None, 'None', 'none']
and not (module.params['auth']
and module.params['auth'].get('endpoint'))
):
module.params['auth'] = dict(
endpoint=module.params['ironic_url']
)
node_id = _choose_id_value(module)

View File

@@ -132,10 +132,15 @@ EXAMPLES = '''
delegate_to: localhost
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
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 _choose_id_value(module):
@@ -227,12 +232,11 @@ def _check_set_power_state(module, cloud, node):
def main():
argument_spec = openstack_full_argument_spec(
argument_spec = ironic_argument_spec(
uuid=dict(required=False),
name=dict(required=False),
instance_info=dict(type='dict', required=False),
config_drive=dict(type='raw', required=False),
ironic_url=dict(required=False),
state=dict(required=False, default='present'),
maintenance=dict(required=False),
maintenance_reason=dict(required=False),
@@ -242,28 +246,7 @@ def main():
timeout=dict(required=False, type='int', default=1800),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
if (
module.params['auth_type'] in [None, 'None', 'none']
and module.params['ironic_url'] is None
and not module.params['cloud']
and not (module.params['auth']
and module.params['auth'].get('endpoint'))
):
module.fail_json(msg="Authentication appears to be disabled, "
"Please define either ironic_url, or cloud, "
"or auth.endpoint")
if (
module.params['ironic_url']
and module.params['auth_type'] in [None, 'None', 'none']
and not (module.params['auth']
and module.params['auth'].get('endpoint'))
):
module.params['auth'] = dict(
endpoint=module.params['ironic_url']
)
module = IronicModule(argument_spec, **module_kwargs)
if (
module.params['config_drive']

View File

@@ -114,128 +114,130 @@ zone:
sample: []
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def _system_state_change(state, email, description, ttl, masters, zone):
if state == 'present':
if not zone:
return True
if email is not None and zone.email != email:
return True
if description is not None and zone.description != description:
return True
if ttl is not None and zone.ttl != ttl:
return True
if masters is not None and zone.masters != masters:
return True
if state == 'absent' and zone:
return True
return False
class DnsZoneModule(OpenStackModule):
def _wait(timeout, cloud, zone, state, module, sdk):
"""Wait for a zone to reach the desired state for the given state."""
for count in sdk.utils.iterate_timeout(
timeout,
"Timeout waiting for zone to be %s" % state):
if (state == 'absent' and zone is None) or (state == 'present' and zone and zone.status == 'ACTIVE'):
return
try:
zone = cloud.get_zone(zone.id)
except Exception:
continue
if zone and zone.status == 'ERROR':
module.fail_json(msg="Zone reached ERROR state while waiting for it to be %s" % state)
def main():
argument_spec = openstack_full_argument_spec(
name=dict(required=True),
zone_type=dict(required=False, choices=['primary', 'secondary']),
email=dict(required=False, default=None),
description=dict(required=False, default=None),
ttl=dict(required=False, default=None, type='int'),
masters=dict(required=False, default=None, type='list', elements='str'),
state=dict(default='present', choices=['absent', 'present']),
argument_spec = dict(
name=dict(required=True, type='str'),
zone_type=dict(required=False, choices=['primary', 'secondary'], type='str'),
email=dict(required=False, type='str'),
description=dict(required=False, type='str'),
ttl=dict(required=False, type='int'),
masters=dict(required=False, type='list', elements='str'),
state=dict(default='present', choices=['absent', 'present'], type='str'),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
def _system_state_change(self, state, email, description, ttl, masters, zone):
if state == 'present':
if not zone:
return True
if email is not None and zone.email != email:
return True
if description is not None and zone.description != description:
return True
if ttl is not None and zone.ttl != ttl:
return True
if masters is not None and zone.masters != masters:
return True
if state == 'absent' and zone:
return True
return False
name = module.params.get('name')
state = module.params.get('state')
wait = module.params.get('wait')
timeout = module.params.get('timeout')
def _wait(self, timeout, zone, state):
"""Wait for a zone to reach the desired state for the given state."""
sdk, cloud = openstack_cloud_from_module(module)
try:
zone = cloud.get_zone(name)
for count in self.sdk.utils.iterate_timeout(
timeout,
"Timeout waiting for zone to be %s" % state):
if (state == 'absent' and zone is None) or (state == 'present' and zone and zone.status == 'ACTIVE'):
return
try:
zone = self.conn.get_zone(zone.id)
except Exception:
continue
if zone and zone.status == 'ERROR':
self.fail_json(msg="Zone reached ERROR state while waiting for it to be %s" % state)
def run(self):
name = self.params['name']
state = self.params['state']
wait = self.params['wait']
timeout = self.params['timeout']
zone = self.conn.get_zone(name)
if state == 'present':
zone_type = module.params.get('zone_type')
email = module.params.get('email')
description = module.params.get('description')
ttl = module.params.get('ttl')
masters = module.params.get('masters')
if module.check_mode:
module.exit_json(changed=_system_state_change(state, email,
description, ttl,
masters, zone))
zone_type = self.params['zone_type']
email = self.params['email']
description = self.params['description']
ttl = self.params['ttl']
masters = self.params['masters']
kwargs = {}
if email:
kwargs['email'] = email
if description:
kwargs['description'] = description
if ttl:
kwargs['ttl'] = ttl
if masters:
kwargs['masters'] = masters
if self.ansible.check_mode:
self.exit_json(changed=self._system_state_change(state, email,
description, ttl,
masters, zone))
if zone is None:
zone = cloud.create_zone(
name=name, zone_type=zone_type, email=email,
description=description, ttl=ttl, masters=masters)
zone = self.conn.create_zone(
name=name, zone_type=zone_type, **kwargs)
changed = True
else:
if masters is None:
masters = []
pre_update_zone = zone
changed = _system_state_change(state, email,
description, ttl,
masters, pre_update_zone)
changed = self._system_state_change(state, email,
description, ttl,
masters, pre_update_zone)
if changed:
zone = cloud.update_zone(
name, email=email,
description=description,
ttl=ttl, masters=masters)
zone = self.conn.update_zone(
name, **kwargs)
if wait:
_wait(timeout, cloud, zone, state, module, sdk)
self._wait(timeout, zone, state)
module.exit_json(changed=changed, zone=zone)
self.exit_json(changed=changed, zone=zone)
elif state == 'absent':
if module.check_mode:
module.exit_json(changed=_system_state_change(state, None,
None, None,
None, zone))
if self.ansible.check_mode:
self.exit_json(changed=self._system_state_change(state, None,
None, None,
None, zone))
if zone is None:
changed = False
else:
cloud.delete_zone(name)
self.conn.delete_zone(name)
changed = True
if wait:
_wait(timeout, cloud, zone, state, module, sdk)
self._wait(timeout, zone, state)
module.exit_json(changed=changed)
self.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
def main():
module = DnsZoneModule()
module()
if __name__ == '__main__':

View File

@@ -156,7 +156,10 @@ def main():
try:
service = cloud.get_service(service_name_or_id)
if service is None:
if service is None and state == 'absent':
module.exit_json(changed=False)
elif service is None and state == 'present':
module.fail_json(msg='Service %s does not exist' % service_name_or_id)
filters = dict(service_id=service.id, interface=interface)

View File

@@ -0,0 +1,291 @@
#!/usr/bin/python
# Copyright (c) 2020 Jesper Schmitz Mouridsen.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: lb_health_monitor
author: OpenStack Ansible SIG
short_description: Add/Delete a health m nonitor to a pool in the load balancing service from OpenStack Cloud
description:
- Add or Remove a health monitor to/from a pool in the OpenStack load-balancer service.
options:
name:
type: 'str'
description:
- Name that has to be given to the health monitor
required: true
state:
type: 'str'
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
pool:
required: true
type: 'str'
description:
- The pool name or id to monitor by the health monitor.
type:
type: 'str'
default: HTTP
description:
- One of HTTP, HTTPS, PING, SCTP, TCP, TLS-HELLO, or UDP-CONNECT.
choices: [HTTP, HTTPS, PING, SCTP, TCP, TLS-HELLO, UDP-CONNECT]
delay:
type: 'str'
required: true
description:
- the interval, in seconds, between health checks.
max_retries:
required: true
type: 'str'
description:
- The number of successful checks before changing the operating status of the member to ONLINE.
max_retries_down:
type: 'str'
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:
required: true
description:
- The time, in seconds, after which a health check times out. Must be less than delay
type: int
admin_state_up:
default: True
description:
- The admin state of the helath monitor true for up or false for down
type: bool
expected_codes:
type: 'str'
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
A list, such as 200, 202
A range, such as 200-204
http_method:
type: 'str'
default: GET
choices: ['GET', 'CONNECT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']
description:
- The HTTP method that the health monitor uses for requests. One of CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, or TRACE. The default is GET.
url_path:
type: 'str'
default: '/'
description:
- The HTTP URL path of the request sent by the monitor to test the health of a backend member.
Must be a string that begins with a forward slash (/). The default URL path is /.
requirements: ["openstacksdk"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
#Create a healtmonitor named healthmonitor01 with method HEAD url_path /status and expect code 200
- openstack.cloud.lb_health_monitor:
auth:
auth_url: "{{keystone_url}}"
username: "{{username}}"
password: "{{password}}"
project_domain_name: "{{domain_name}}"
user_domain_name: "{{domain_name}}"
project_name: "{{project_name}}"
wait: true
admin_state_up: True
expected_codes: '200'
max_retries_down: '4'
http_method: GET
url_path: "/status"
pool: '{{pool_id}}'
name: 'healthmonitor01'
delay: '10'
max_retries: '3'
resp_timeout: '5'
state: present
'''
RETURN = '''
health_monitor:
description: Dictionary describing the health monitor.
returned: On success when C(state=present)
type: complex
contains:
id:
description: The health monitor UUID.
returned: On success when C(state=present)
type: str
admin_state_up:
returned: On success when C(state=present)
description: The administrative state of the resource.
type: bool
created_at:
returned: On success when C(state=present)
description: The UTC date and timestamp when the resource was created.
type: str
delay:
returned: On success when C(state=present)
description: The time, in seconds, between sending probes to members.
type: int
expected_codes:
returned: On success when C(state=present)
description: The list of HTTP status codes expected in response from the member to declare it healthy.
type: str
http_method:
returned: On success when C(state=present)
description: The HTTP method that the health monitor uses for requests.
type: str
max_retries:
returned: On success when C(state=present)
description: The number of successful checks before changing the operating status of the member to ONLINE.
type: str
max_retries_down:
returned: On success when C(state=present)
description: The number of allowed check failures before changing the operating status of the member to ERROR.
type: str
name:
returned: On success when C(state=present)
description: Human-readable name of the resource.
type: str
operating_status:
returned: On success when C(state=present)
description: The operating status of the resource.
type: str
pool_id:
returned: On success when C(state=present)
description: The id of the pool.
type: str
project_id:
returned: On success when C(state=present)
description: The ID of the project owning this resource.
type: str
provisioning_status:
returned: On success when C(state=present)
description: The provisioning status of the resource.
type: str
timeout:
returned: On success when C(state=present)
description: The maximum time, in seconds, that a monitor waits to connect before it times out.
type: int
type:
returned: On success when C(state=present)
description: The type of health monitor.
type: str
updated_at:
returned: On success when C(state=present)
description: The UTC date and timestamp when the resource was last updated.
type: str
url_path:
returned: On success when C(state=present)
description: The HTTP URL path of the request sent by the monitor to test the health of a backend member.
type: str
'''
import time
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class HealthMonitorModule(OpenStackModule):
def _wait_for_health_monitor_status(self, health_monitor_id, status, failures, interval=5):
timeout = self.params['timeout']
total_sleep = 0
if failures is None:
failures = []
while total_sleep < timeout:
health_monitor = self.conn.load_balancer.get_health_monitor(health_monitor_id)
provisioning_status = health_monitor.provisioning_status
if provisioning_status == status:
return health_monitor
if provisioning_status in failures:
self._fail_json(
msg="health monitor %s transitioned to failure state %s" %
(health_monitor, provisioning_status)
)
time.sleep(interval)
total_sleep += interval
self._fail_json(msg="timeout waiting for health monitor %s to transition to %s" %
(health_monitor_id, status)
)
argument_spec = dict(
name=dict(required=True),
delay=dict(required=True),
max_retries=dict(required=True),
max_retries_down=dict(required=False, default="3"),
resp_timeout=dict(required=True, type='int'),
pool=dict(required=True),
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",
"HEAD", "OPTIONS", "PATCH",
"POST", "PUT", "TRACE"]),
url_path=dict(default="/", requires=False),
type=dict(default='HTTP',
choices=['HTTP', 'HTTPS', 'PING', 'SCTP', 'TCP', 'TLS-HELLO', 'UDP-CONNECT']))
module_kwargs = dict(supports_check_mode=True)
def run(self):
try:
changed = False
health_monitor = self.conn.load_balancer.find_health_monitor(name_or_id=self.params['name'])
pool = self.conn.load_balancer.find_pool(name_or_id=self.params['pool'])
if self.params['state'] == 'present':
if not health_monitor:
changed = True
health_attrs = {"pool_id": pool.id,
"type": self.params["type"],
"delay": self.params['delay'],
"max_retries": self.params['max_retries'],
"max_retries_down": self.params['max_retries_down'],
"timeout": self.params['resp_timeout'],
"name": self.params['name'],
"admin_state_up": self.params["admin_state_up"],
}
if self.params["type"] in ["HTTP", "HTTPS"]:
health_attrs["expected_codes"] = self.params["expected_codes"]
health_attrs["http_method"] = self.params["http_method"]
health_attrs["url_path"] = self.params["url_path"]
if self.ansible.check_mode:
self.exit_json(changed=True)
health_monitor = self.conn.load_balancer.create_health_monitor(**health_attrs)
if not self.params['wait']:
self.exit_json(changed=changed, id=health_monitor.id,
health_monitor=health_monitor.to_dict())
else:
health_monitor = self._wait_for_health_monitor_status(health_monitor.id, "ACTIVE", ["ERROR"])
self.exit_json(changed=changed, id=health_monitor.id,
health_monitor=health_monitor.to_dict())
else:
self.exit_json(changed=changed, id=health_monitor.id,
health_monitor=health_monitor.to_dict()
)
elif self.params['state'] == 'absent':
if health_monitor:
if self.ansible.check_mode:
self.exit_json(changed=True)
self.conn.load_balancer.delete_health_monitor(health_monitor)
changed = True
self.exit_json(changed=changed)
except Exception as e:
self.fail(msg=str(e))
def main():
module = HealthMonitorModule()
module()
if __name__ == "__main__":
main()

View File

@@ -83,6 +83,12 @@ options:
description:
- The protocol port number for the listener.
default: 80
allowed_cidrs:
description:
- A list of IPv4, IPv6 or mix of both CIDRs to be allowed access to the listener. The default is all allowed.
When a list of CIDRs is provided, the default switches to deny all.
Ignored on unsupported Octavia versions (less than 2.12)
default: []
pool:
description:
- The pool attached to the listener.
@@ -285,51 +291,47 @@ EXAMPLES = '''
'''
import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def _wait_for_lb(module, cloud, lb, status, failures, interval=5):
"""Wait for load balancer to be in a particular provisioning status."""
timeout = module.params['timeout']
class LoadBalancerModule(OpenStackModule):
total_sleep = 0
if failures is None:
failures = []
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']
while total_sleep < timeout:
lb = cloud.load_balancer.find_load_balancer(lb.id)
total_sleep = 0
if failures is None:
failures = []
if lb:
if lb.provisioning_status == status:
return None
if lb.provisioning_status in failures:
module.fail_json(
msg="Load Balancer %s transitioned to failure state %s" %
(lb.id, lb.provisioning_status)
)
else:
if status == "DELETED":
return None
while total_sleep < timeout:
lb = self.conn.load_balancer.find_load_balancer(lb.id)
if lb:
if lb.provisioning_status == status:
return None
if lb.provisioning_status in failures:
self.fail_json(
msg="Load Balancer %s transitioned to failure state %s" %
(lb.id, lb.provisioning_status)
)
else:
module.fail_json(
msg="Load Balancer %s transitioned to DELETED" % lb.id
)
if status == "DELETED":
return None
else:
self.fail_json(
msg="Load Balancer %s transitioned to DELETED" % lb.id
)
time.sleep(interval)
total_sleep += interval
time.sleep(interval)
total_sleep += interval
module.fail_json(
msg="Timeout waiting for Load Balancer %s to transition to %s" %
(lb.id, status)
)
self.fail_json(
msg="Timeout waiting for Load Balancer %s to transition to %s" %
(lb.id, status)
)
def main():
argument_spec = openstack_full_argument_spec(
argument_spec = dict(
name=dict(required=True),
flavor=dict(required=False),
state=dict(default='present', choices=['absent', 'present']),
@@ -343,162 +345,185 @@ def main():
public_network=dict(required=False),
delete_public_ip=dict(required=False, default=False, type='bool'),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
sdk, cloud = openstack_cloud_from_module(module)
module_kwargs = dict(supports_check_mode=True)
flavor = module.params['flavor']
vip_network = module.params['vip_network']
vip_subnet = module.params['vip_subnet']
vip_port = module.params['vip_port']
listeners = module.params['listeners']
public_vip_address = module.params['public_ip_address']
allocate_fip = module.params['auto_public_ip']
delete_fip = module.params['delete_public_ip']
public_network = module.params['public_network']
def run(self):
flavor = self.params['flavor']
vip_network = self.params['vip_network']
vip_subnet = self.params['vip_subnet']
vip_port = self.params['vip_port']
listeners = self.params['listeners']
public_vip_address = self.params['public_ip_address']
allocate_fip = self.params['auto_public_ip']
delete_fip = self.params['delete_public_ip']
public_network = self.params['public_network']
vip_network_id = None
vip_subnet_id = None
vip_port_id = None
flavor_id = None
vip_network_id = None
vip_subnet_id = None
vip_port_id = None
flavor_id = None
try:
changed = False
lb = cloud.load_balancer.find_load_balancer(
name_or_id=module.params['name'])
try:
max_microversion = 1
max_majorversion = 2
changed = False
lb = self.conn.load_balancer.find_load_balancer(
name_or_id=self.params['name'])
if module.params['state'] == 'present':
if not lb:
if not (vip_network or vip_subnet or vip_port):
module.fail_json(
msg="One of vip_network, vip_subnet, or vip_port must "
"be specified for load balancer creation"
)
if self.params['state'] == 'present':
if lb and self.ansible.check_mode:
self.exit_json(changed=False)
if lb:
self.exit_json(changed=False)
ver_data = self.conn.load_balancer.get_all_version_data()
region = list(ver_data.keys())[0]
interface_type = list(ver_data[region].keys())[0]
versions = ver_data[region][interface_type]['load-balancer']
for ver in versions:
if ver['status'] == 'CURRENT':
curversion = ver['version'].split(".")
max_majorversion = int(curversion[0])
max_microversion = int(curversion[1])
if flavor:
_flavor = cloud.load_balancer.find_flavor(flavor)
if not _flavor:
module.fail_json(
msg='flavor %s not found' % flavor
if not lb:
if self.ansible.check_mode:
self.exit_json(changed=True)
if not (vip_network or vip_subnet or vip_port):
self.fail_json(
msg="One of vip_network, vip_subnet, or vip_port must "
"be specified for load balancer creation"
)
flavor_id = _flavor.id
if vip_network:
network = cloud.get_network(vip_network)
if not network:
module.fail_json(
msg='network %s is not found' % vip_network
)
vip_network_id = network.id
if vip_subnet:
subnet = cloud.get_subnet(vip_subnet)
if not subnet:
module.fail_json(
msg='subnet %s is not found' % vip_subnet
)
vip_subnet_id = subnet.id
if vip_port:
port = cloud.get_port(vip_port)
if not port:
module.fail_json(
msg='port %s is not found' % vip_port
)
vip_port_id = port.id
if flavor:
_flavor = self.conn.load_balancer.find_flavor(flavor)
if not _flavor:
self.fail_json(
msg='flavor %s not found' % flavor
)
flavor_id = _flavor.id
lb = cloud.load_balancer.create_load_balancer(
name=module.params['name'],
flavor_id=flavor_id,
vip_network_id=vip_network_id,
vip_subnet_id=vip_subnet_id,
vip_port_id=vip_port_id,
vip_address=module.params['vip_address'],
)
changed = True
if vip_network:
network = self.conn.get_network(vip_network)
if not network:
self.fail_json(
msg='network %s is not found' % vip_network
)
vip_network_id = network.id
if vip_subnet:
subnet = self.conn.get_subnet(vip_subnet)
if not subnet:
self.fail_json(
msg='subnet %s is not found' % vip_subnet
)
vip_subnet_id = subnet.id
if vip_port:
port = self.conn.get_port(vip_port)
if not listeners and not module.params['wait']:
module.exit_json(
changed=changed,
loadbalancer=lb.to_dict(),
id=lb.id
)
if not port:
self.fail_json(
msg='port %s is not found' % vip_port
)
vip_port_id = port.id
lbargs = {"name": self.params['name'],
"vip_network_id": vip_network_id,
"vip_subnet_id": vip_subnet_id,
"vip_port_id": vip_port_id,
"vip_address": self.params['vip_address']
}
if flavor_id is not None:
lbargs["flavor_id"] = flavor_id
_wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"])
lb = self.conn.load_balancer.create_load_balancer(**lbargs)
for listener_def in listeners:
listener_name = listener_def.get("name")
pool_def = listener_def.get("pool")
if not listener_name:
module.fail_json(msg='listener name is required')
listener = cloud.load_balancer.find_listener(
name_or_id=listener_name
)
if not listener:
_wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"])
protocol = listener_def.get("protocol", "HTTP")
protocol_port = listener_def.get("protocol_port", 80)
listener = cloud.load_balancer.create_listener(
name=listener_name,
loadbalancer_id=lb.id,
protocol=protocol,
protocol_port=protocol_port,
)
changed = True
# Ensure pool in the listener.
if pool_def:
pool_name = pool_def.get("name")
members = pool_def.get('members', [])
if not listeners and not self.params['wait']:
self.exit_json(
changed=changed,
loadbalancer=lb.to_dict(),
id=lb.id
)
if not pool_name:
module.fail_json(msg='pool name is required')
self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
pool = cloud.load_balancer.find_pool(name_or_id=pool_name)
for listener_def in listeners:
listener_name = listener_def.get("name")
pool_def = listener_def.get("pool")
if not pool:
_wait_for_lb(module, cloud, lb, "ACTIVE", ["ERROR"])
if not listener_name:
self.fail_json(msg='listener name is required')
protocol = pool_def.get("protocol", "HTTP")
lb_algorithm = pool_def.get("lb_algorithm",
"ROUND_ROBIN")
listener = self.conn.load_balancer.find_listener(
name_or_id=listener_name
)
pool = cloud.load_balancer.create_pool(
name=pool_name,
listener_id=listener.id,
protocol=protocol,
lb_algorithm=lb_algorithm
)
if not listener:
self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
protocol = listener_def.get("protocol", "HTTP")
protocol_port = listener_def.get("protocol_port", 80)
allowed_cidrs = listener_def.get("allowed_cidrs", [])
listenerargs = {"name": listener_name,
"loadbalancer_id": lb.id,
"protocol": protocol,
"protocol_port": protocol_port
}
if max_microversion >= 12 and max_majorversion >= 2:
listenerargs['allowed_cidrs'] = allowed_cidrs
listener = self.conn.load_balancer.create_listener(**listenerargs)
changed = True
# Ensure pool in the listener.
if pool_def:
pool_name = pool_def.get("name")
members = pool_def.get('members', [])
if not pool_name:
self.fail_json(msg='pool name is required')
pool = self.conn.load_balancer.find_pool(name_or_id=pool_name)
if not pool:
self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
protocol = pool_def.get("protocol", "HTTP")
lb_algorithm = pool_def.get("lb_algorithm",
"ROUND_ROBIN")
pool = self.conn.load_balancer.create_pool(
name=pool_name,
listener_id=listener.id,
protocol=protocol,
lb_algorithm=lb_algorithm
)
changed = True
# Ensure members in the pool
for member_def in members:
member_name = member_def.get("name")
if not member_name:
module.fail_json(msg='member name is required')
for member_def in members:
member_name = member_def.get("name")
if not member_name:
self.fail_json(msg='member name is required')
member = cloud.load_balancer.find_member(member_name,
pool.id)
member = self.conn.load_balancer.find_member(member_name,
pool.id
)
if not member:
_wait_for_lb(module, cloud, lb, "ACTIVE",
["ERROR"])
if not member:
self._wait_for_lb(lb, "ACTIVE", ["ERROR"])
address = member_def.get("address")
if not address:
module.fail_json(
self.fail_json(
msg='member address for member %s is '
'required' % member_name
)
subnet_id = member_def.get("subnet")
if subnet_id:
subnet = cloud.get_subnet(subnet_id)
subnet = self.conn.get_subnet(subnet_id)
if not subnet:
module.fail_json(
self.fail_json(
msg='subnet %s for member %s is not '
'found' % (subnet_id, member_name)
)
@@ -506,7 +531,7 @@ def main():
protocol_port = member_def.get("protocol_port", 80)
member = cloud.load_balancer.create_member(
member = self.conn.load_balancer.create_member(
pool,
name=member_name,
address=address,
@@ -515,110 +540,120 @@ def main():
)
changed = True
# Associate public ip to the load balancer VIP. If
# public_vip_address is provided, use that IP, otherwise, either
# find an available public ip or create a new one.
fip = None
orig_public_ip = None
new_public_ip = None
if public_vip_address or allocate_fip:
ips = cloud.network.ips(
port_id=lb.vip_port_id,
fixed_ip_address=lb.vip_address
)
ips = list(ips)
if ips:
orig_public_ip = ips[0]
new_public_ip = orig_public_ip.floating_ip_address
if public_vip_address and public_vip_address != orig_public_ip:
fip = cloud.network.find_ip(public_vip_address)
if not fip:
module.fail_json(
msg='Public IP %s is unavailable' % public_vip_address
)
# Release origin public ip first
cloud.network.update_ip(
orig_public_ip,
fixed_ip_address=None,
port_id=None
)
# Associate new public ip
cloud.network.update_ip(
fip,
fixed_ip_address=lb.vip_address,
port_id=lb.vip_port_id
)
new_public_ip = public_vip_address
changed = True
elif allocate_fip and not orig_public_ip:
fip = cloud.network.find_available_ip()
if not fip:
if not public_network:
module.fail_json(msg="Public network is not provided")
pub_net = cloud.network.find_network(public_network)
if not pub_net:
module.fail_json(
msg='Public network %s not found' %
public_network
)
fip = cloud.network.create_ip(
floating_network_id=pub_net.id
)
cloud.network.update_ip(
fip,
fixed_ip_address=lb.vip_address,
port_id=lb.vip_port_id
)
new_public_ip = fip.floating_ip_address
changed = True
# Include public_vip_address in the result.
lb = cloud.load_balancer.find_load_balancer(name_or_id=lb.id)
lb_dict = lb.to_dict()
lb_dict.update({"public_vip_address": new_public_ip})
module.exit_json(
changed=changed,
loadbalancer=lb_dict,
id=lb.id
)
elif module.params['state'] == 'absent':
changed = False
public_vip_address = None
if lb:
if delete_fip:
ips = cloud.network.ips(
# Associate public ip to the load balancer VIP. If
# public_vip_address is provided, use that IP, otherwise, either
# find an available public ip or create a new one.
fip = None
orig_public_ip = None
new_public_ip = None
if public_vip_address or allocate_fip:
ips = self.conn.network.ips(
port_id=lb.vip_port_id,
fixed_ip_address=lb.vip_address
)
ips = list(ips)
if ips:
public_vip_address = ips[0]
orig_public_ip = ips[0]
new_public_ip = orig_public_ip.floating_ip_address
# Deleting load balancer with `cascade=False` does not make
# sense because the deletion will always fail if there are
# sub-resources.
cloud.load_balancer.delete_load_balancer(lb, cascade=True)
changed = True
if public_vip_address and public_vip_address != orig_public_ip:
fip = self.conn.network.find_ip(public_vip_address)
if module.params['wait']:
_wait_for_lb(module, cloud, lb, "DELETED", ["ERROR"])
if not fip:
self.fail_json(
msg='Public IP %s is unavailable' % public_vip_address
)
if delete_fip and public_vip_address:
cloud.network.delete_ip(public_vip_address)
changed = True
# Release origin public ip first
self.conn.network.update_ip(
orig_public_ip,
fixed_ip_address=None,
port_id=None
)
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e), extra_data=e.extra_data)
# Associate new public ip
self.conn.network.update_ip(
fip,
fixed_ip_address=lb.vip_address,
port_id=lb.vip_port_id
)
new_public_ip = public_vip_address
changed = True
elif allocate_fip and not orig_public_ip:
fip = self.conn.network.find_available_ip()
if not fip:
if not public_network:
self.fail_json(msg="Public network is not provided")
pub_net = self.conn.network.find_network(public_network)
if not pub_net:
self.fail_json(
msg='Public network %s not found' %
public_network
)
fip = self.conn.network.create_ip(
floating_network_id=pub_net.id
)
self.conn.network.update_ip(
fip,
fixed_ip_address=lb.vip_address,
port_id=lb.vip_port_id
)
new_public_ip = fip.floating_ip_address
changed = True
# Include public_vip_address in the result.
lb = self.conn.load_balancer.find_load_balancer(name_or_id=lb.id)
lb_dict = lb.to_dict()
lb_dict.update({"public_vip_address": new_public_ip})
self.exit_json(
changed=changed,
loadbalancer=lb_dict,
id=lb.id
)
elif self.params['state'] == 'absent':
changed = False
public_vip_address = None
if lb:
if self.ansible.check_mode:
self.exit_json(changed=True)
if delete_fip:
ips = self.conn.network.ips(
port_id=lb.vip_port_id,
fixed_ip_address=lb.vip_address
)
ips = list(ips)
if ips:
public_vip_address = ips[0]
# Deleting load balancer with `cascade=False` does not make
# sense because the deletion will always fail if there are
# sub-resources.
self.conn.load_balancer.delete_load_balancer(lb, cascade=True)
changed = True
if self.params['wait']:
self._wait_for_lb(lb, "DELETED", ["ERROR"])
if delete_fip and public_vip_address:
self.conn.network.delete_ip(public_vip_address)
changed = True
elif self.ansible.check_mode:
self.exit_json(changed=False)
self.exit_json(changed=changed)
except Exception as e:
self.fail_json(msg=str(e))
def main():
module = LoadBalancerModule()
module()
if __name__ == "__main__":

View File

@@ -63,12 +63,13 @@ options:
Network will use OpenStack defaults if this option is
not utilised. Requires openstacksdk>=0.18.
type: bool
mtu:
mtu_size:
description:
- The maximum transmission unit (MTU) value to address fragmentation.
Network will use OpenStack defaults if this option is
not provided. Requires openstacksdk>=0.18.
type: int
aliases: ['mtu']
dns_domain:
description:
- The DNS domain value to set. Requires openstacksdk>=0.29.
@@ -156,14 +157,12 @@ network:
sample: 101
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def main():
argument_spec = openstack_full_argument_spec(
class NetworkModule(OpenStackModule):
argument_spec = dict(
name=dict(required=True),
shared=dict(default=False, type='bool'),
admin_state_up=dict(default=True, type='bool'),
@@ -173,51 +172,38 @@ def main():
provider_segmentation_id=dict(required=False, type='int'),
state=dict(default='present', choices=['absent', 'present']),
project=dict(default=None),
port_security_enabled=dict(type='bool'),
mtu=dict(required=False, type='int'),
dns_domain=dict(required=False)
port_security_enabled=dict(type='bool', min_ver='0.18.0'),
mtu_size=dict(required=False, type='int', min_ver='0.18.0', aliases=['mtu']),
dns_domain=dict(required=False, min_ver='0.29.0')
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
def run(self):
state = module.params['state']
name = module.params['name']
shared = module.params['shared']
admin_state_up = module.params['admin_state_up']
external = module.params['external']
provider_physical_network = module.params['provider_physical_network']
provider_network_type = module.params['provider_network_type']
provider_segmentation_id = module.params['provider_segmentation_id']
project = module.params['project']
state = self.params['state']
name = self.params['name']
shared = self.params['shared']
admin_state_up = self.params['admin_state_up']
external = self.params['external']
provider_physical_network = self.params['provider_physical_network']
provider_network_type = self.params['provider_network_type']
provider_segmentation_id = self.params['provider_segmentation_id']
project = self.params['project']
net_create_kwargs = {}
min_version = None
kwargs = self.check_versioned(
mtu_size=self.params['mtu_size'], port_security_enabled=self.params['port_security_enabled'],
dns_domain=self.params['dns_domain']
)
if module.params['mtu'] is not None:
min_version = '0.18.0'
net_create_kwargs['mtu_size'] = module.params['mtu']
if module.params['port_security_enabled'] is not None:
min_version = '0.18.0'
net_create_kwargs['port_security_enabled'] = module.params['port_security_enabled']
if module.params['dns_domain'] is not None:
min_version = '0.29.0'
net_create_kwargs['dns_domain'] = module.params['dns_domain']
sdk, cloud = openstack_cloud_from_module(module, min_version)
try:
if project is not None:
proj = cloud.get_project(project)
proj = self.conn.get_project(project)
if proj is None:
module.fail_json(msg='Project %s could not be found' % project)
self.fail_json(msg='Project %s could not be found' % project)
project_id = proj['id']
filters = {'tenant_id': project_id}
else:
project_id = None
filters = None
net = cloud.get_network(name, filters=filters)
net = self.conn.get_network(name, filters=filters)
if state == 'present':
if not net:
@@ -230,28 +216,30 @@ def main():
provider['segmentation_id'] = provider_segmentation_id
if project_id is not None:
net = cloud.create_network(name, shared, admin_state_up,
external, provider, project_id,
**net_create_kwargs)
net = self.conn.create_network(name, shared, admin_state_up,
external, provider, project_id,
**kwargs)
else:
net = cloud.create_network(name, shared, admin_state_up,
external, provider,
**net_create_kwargs)
net = self.conn.create_network(name, shared, admin_state_up,
external, provider,
**kwargs)
changed = True
else:
changed = False
module.exit_json(changed=changed, network=net, id=net['id'])
self.exit(changed=changed, network=net, id=net['id'])
elif state == 'absent':
if not net:
module.exit_json(changed=False)
self.exit(changed=False)
else:
cloud.delete_network(name)
module.exit_json(changed=True)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
self.conn.delete_network(name)
self.exit(changed=True)
if __name__ == "__main__":
def main():
module = NetworkModule()
module()
if __name__ == '__main__':
main()

View File

@@ -113,37 +113,33 @@ openstack_networks:
type: bool
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
openstack_full_argument_spec,
openstack_cloud_from_module,
)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def main():
class NetworkInfoModule(OpenStackModule):
argument_spec = openstack_full_argument_spec(
deprecated_names = ('networks_facts', 'openstack.cloud.networks_facts')
argument_spec = dict(
name=dict(required=False, default=None),
filters=dict(required=False, type='dict', default=None)
)
module = AnsibleModule(argument_spec)
is_old_facts = module._name == 'openstack.cloud.networks_facts'
if is_old_facts:
module.deprecate("The 'openstack.cloud.networks_facts' module has been renamed to 'openstack.cloud.networks_info', "
"and the renamed one no longer returns ansible_facts", version='2.13')
sdk, cloud = openstack_cloud_from_module(module)
try:
networks = cloud.search_networks(module.params['name'],
module.params['filters'])
if is_old_facts:
module.exit_json(changed=False, ansible_facts=dict(
openstack_networks=networks))
else:
module.exit_json(changed=False, openstack_networks=networks)
def run(self):
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
kwargs = self.check_versioned(
filters=self.params['filters']
)
if self.params['name']:
kwargs['name_or_id'] = self.params['name']
networks = self.conn.search_networks(**kwargs)
self.exit(changed=False, openstack_networks=networks)
def main():
module = NetworkInfoModule()
module()
if __name__ == '__main__':

View File

@@ -19,13 +19,14 @@ options:
type: str
name:
description:
- Name of the recordset
- Name of the recordset. It must be ended with name of dns zone.
required: true
type: str
recordset_type:
description:
- Recordset type
- Required when I(state=present).
choices: ['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']
type: str
records:
description:
@@ -61,8 +62,8 @@ EXAMPLES = '''
cloud: mycloud
state: present
zone: example.net.
name: www
recordset_type: primary
name: www.example.net.
recordset_type: "a"
records: ['10.1.1.1']
description: test recordset
ttl: 3600
@@ -72,7 +73,7 @@ EXAMPLES = '''
cloud: mycloud
state: present
zone: example.net.
name: www
name: www.example.net.
ttl: 7200
# Delete recordset named "www.example.net."
@@ -80,7 +81,7 @@ EXAMPLES = '''
cloud: mycloud
state: absent
zone: example.net.
name: www
name: www.example.net.
'''
RETURN = '''
@@ -125,7 +126,7 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
openstack_cloud_from_module)
def _system_state_change(state, records, description, ttl, zone, recordset):
def _system_state_change(state, records, description, ttl, recordset):
if state == 'present':
if recordset is None:
return True
@@ -144,10 +145,10 @@ def main():
argument_spec = openstack_full_argument_spec(
zone=dict(required=True),
name=dict(required=True),
recordset_type=dict(required=False),
recordset_type=dict(required=False, choices=['a', 'aaaa', 'mx', 'cname', 'txt', 'ns', 'srv', 'ptr', 'caa']),
records=dict(required=False, type='list', elements='str'),
description=dict(required=False, default=None),
ttl=dict(required=False, default=None, type='int'),
ttl=dict(required=False, type='int'),
state=dict(default='present', choices=['absent', 'present']),
)
@@ -159,76 +160,77 @@ def main():
supports_check_mode=True,
**module_kwargs)
module.module_min_sdk_version = '0.28.0'
zone = module.params.get('zone')
name = module.params.get('name')
state = module.params.get('state')
sdk, cloud = openstack_cloud_from_module(module)
try:
recordset_type = module.params.get('recordset_type')
recordset_filter = {'type': recordset_type}
recordsets = cloud.search_recordsets(zone, name_or_id=name)
recordsets = cloud.search_recordsets(zone, name_or_id=name, filters=recordset_filter)
if recordsets:
recordset = recordsets[0]
try:
recordset_id = recordset['id']
except KeyError as e:
module.fail_json(msg=str(e))
else:
# recordsets is filtered by type and should never be more than 1 return
recordset = None
if len(recordsets) == 1:
recordset = recordsets[0]
try:
recordset_id = recordset['id']
except KeyError as e:
module.fail_json(msg=str(e))
if state == 'present':
recordset_type = module.params.get('recordset_type').upper()
records = module.params.get('records')
description = module.params.get('description')
ttl = module.params.get('ttl')
kwargs = {}
if description:
kwargs['description'] = description
kwargs['records'] = records
if module.check_mode:
module.exit_json(changed=_system_state_change(state,
records, description,
ttl, recordset))
if recordset is None:
if ttl:
kwargs['ttl'] = ttl
else:
kwargs['ttl'] = 300
recordset = cloud.create_recordset(
zone=zone, name=name, recordset_type=recordset_type,
**kwargs)
changed = True
else:
# recordsets is filtered by type and should never be more than 1 return
recordset = None
if state == 'present':
records = module.params.get('records')
description = module.params.get('description')
ttl = module.params.get('ttl')
if ttl:
kwargs['ttl'] = ttl
if module.check_mode:
module.exit_json(changed=_system_state_change(state,
records, description,
ttl, zone,
recordset))
pre_update_recordset = recordset
changed = _system_state_change(state, records,
description, ttl,
pre_update_recordset)
if changed:
recordset = cloud.update_recordset(
zone=zone, name_or_id=recordset_id, **kwargs)
if recordset is None:
recordset = cloud.create_recordset(
zone=zone, name=name, recordset_type=recordset_type,
records=records, description=description, ttl=ttl)
changed = True
else:
if records is None:
records = []
module.exit_json(changed=changed, recordset=recordset)
pre_update_recordset = recordset
changed = _system_state_change(state, records,
description, ttl,
zone, pre_update_recordset)
if changed:
zone = cloud.update_recordset(
zone, recordset_id,
records=records,
description=description,
ttl=ttl)
elif state == 'absent':
if module.check_mode:
module.exit_json(changed=_system_state_change(state,
None, None,
None, recordset))
module.exit_json(changed=changed, recordset=recordset)
elif state == 'absent':
if module.check_mode:
module.exit_json(changed=_system_state_change(state,
None, None,
None,
None, recordset))
if recordset is None:
changed = False
else:
cloud.delete_recordset(zone, recordset_id)
changed = True
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if recordset is None:
changed = False
else:
cloud.delete_recordset(zone, recordset_id)
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':

View File

@@ -210,10 +210,8 @@ router:
type: list
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
ROUTER_INTERFACE_OWNERS = set([
'network:router_interface',
@@ -222,160 +220,8 @@ ROUTER_INTERFACE_OWNERS = set([
])
def _router_internal_interfaces(cloud, router):
for port in cloud.list_router_interfaces(router, 'internal'):
if port['device_owner'] in ROUTER_INTERFACE_OWNERS:
yield port
def _needs_update(cloud, module, router, network, internal_subnet_ids, internal_port_ids, filters=None):
"""Decide if the given router needs an update.
"""
if router['admin_state_up'] != module.params['admin_state_up']:
return True
if router['external_gateway_info']:
# check if enable_snat is set in module params
if module.params['enable_snat'] is not None:
if router['external_gateway_info'].get('enable_snat', True) != module.params['enable_snat']:
return True
if network:
if not router['external_gateway_info']:
return True
elif router['external_gateway_info']['network_id'] != network['id']:
return True
# check external interfaces
if module.params['external_fixed_ips']:
for new_iface in module.params['external_fixed_ips']:
subnet = cloud.get_subnet(new_iface['subnet'], filters)
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']:
# 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 internal interfaces
if module.params['interfaces']:
existing_subnet_ids = []
for port in _router_internal_interfaces(cloud, router):
if 'fixed_ips' in port:
for fixed_ip in port['fixed_ips']:
existing_subnet_ids.append(fixed_ip['subnet_id'])
for iface in module.params['interfaces']:
if isinstance(iface, dict):
for p_id in internal_port_ids:
p = cloud.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):
internal_subnet_ids = []
return True
return False
def _system_state_change(cloud, module, router, network, internal_ids, internal_portids, filters=None):
"""Check if the system state would be changed."""
state = module.params['state']
if state == 'absent' and router:
return True
if state == 'present':
if not router:
return True
return _needs_update(cloud, module, router, network, internal_ids, internal_portids, filters)
return False
def _build_kwargs(cloud, module, router, network):
kwargs = {
'admin_state_up': module.params['admin_state_up'],
}
if router:
kwargs['name_or_id'] = router['id']
else:
kwargs['name'] = module.params['name']
if network:
kwargs['ext_gateway_net_id'] = network['id']
# can't send enable_snat unless we have a network
if module.params.get('enable_snat') is not None:
kwargs['enable_snat'] = module.params['enable_snat']
if module.params['external_fixed_ips']:
kwargs['ext_fixed_ips'] = []
for iface in module.params['external_fixed_ips']:
subnet = cloud.get_subnet(iface['subnet'])
d = {'subnet_id': subnet['id']}
if 'ip' in iface:
d['ip_address'] = iface['ip']
kwargs['ext_fixed_ips'].append(d)
return kwargs
def _validate_subnets(module, cloud, filters=None):
external_subnet_ids = []
internal_subnet_ids = []
internal_port_ids = []
existing_port_ips = []
if module.params['external_fixed_ips']:
for iface in module.params['external_fixed_ips']:
subnet = cloud.get_subnet(iface['subnet'])
if not subnet:
module.fail_json(msg='subnet %s not found' % iface['subnet'])
external_subnet_ids.append(subnet['id'])
if module.params['interfaces']:
for iface in module.params['interfaces']:
if isinstance(iface, str):
subnet = cloud.get_subnet(iface, filters)
if not subnet:
module.fail_json(msg='subnet %s not found' % iface)
internal_subnet_ids.append(subnet['id'])
elif isinstance(iface, dict):
subnet = cloud.get_subnet(iface['subnet'], filters)
if not subnet:
module.fail_json(msg='subnet %s not found' % iface['subnet'])
net = cloud.get_network(iface['net'])
if not net:
module.fail_json(msg='net %s not found' % iface['net'])
if "portip" not in iface:
internal_subnet_ids.append(subnet['id'])
elif not iface['portip']:
module.fail_json(msg='put an ip in portip or remove it from list to assign default port to router')
else:
for existing_port in cloud.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 = cloud.create_port(network_id=net.id, fixed_ips=[{'ip_address': iface['portip'], 'subnet_id': subnet.id}])
if p:
internal_port_ids.append(p.id)
return external_subnet_ids, internal_subnet_ids, internal_port_ids
def main():
argument_spec = openstack_full_argument_spec(
class RouterModule(OpenStackModule):
argument_spec = dict(
state=dict(default='present', choices=['absent', 'present']),
name=dict(required=True),
admin_state_up=dict(type='bool', default=True),
@@ -386,65 +232,210 @@ def main():
project=dict(default=None)
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
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
state = module.params['state']
name = module.params['name']
network = module.params['network']
project = module.params['project']
def _needs_update(self, router, network, internal_subnet_ids, internal_port_ids, filters=None):
"""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']:
# check if enable_snat is set in module params
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 not router['external_gateway_info']:
return True
elif router['external_gateway_info']['network_id'] != network['id']:
return True
if module.params['external_fixed_ips'] and not network:
module.fail_json(msg='network is required when supplying external_fixed_ips')
# 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
# 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']:
# 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 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):
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:
return True
if state == 'present':
if not router:
return True
return self._needs_update(router, network, internal_ids, internal_portids, filters)
return False
def _build_kwargs(self, router, network):
kwargs = {
'admin_state_up': self.params['admin_state_up'],
}
if router:
kwargs['name_or_id'] = router['id']
else:
kwargs['name'] = self.params['name']
if network:
kwargs['ext_gateway_net_id'] = network['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']
if self.params['external_fixed_ips']:
kwargs['ext_fixed_ips'] = []
for iface in self.params['external_fixed_ips']:
subnet = self.conn.get_subnet(iface['subnet'])
d = {'subnet_id': subnet['id']}
if 'ip' in iface:
d['ip_address'] = iface['ip']
kwargs['ext_fixed_ips'].append(d)
return kwargs
def _validate_subnets(self, filters=None):
external_subnet_ids = []
internal_subnet_ids = []
internal_port_ids = []
existing_port_ips = []
if self.params['external_fixed_ips']:
for iface in self.params['external_fixed_ips']:
subnet = self.conn.get_subnet(iface['subnet'])
if not subnet:
self.fail_json(msg='subnet %s not found' % iface['subnet'])
external_subnet_ids.append(subnet['id'])
if self.params['interfaces']:
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'])
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'])
elif not iface['portip']:
self.fail(msg='put an ip in portip or remove it from list to assign default port to router')
else:
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)
return external_subnet_ids, internal_subnet_ids, internal_port_ids
def run(self):
state = self.params['state']
name = self.params['name']
network = self.params['network']
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')
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
proj = self.conn.get_project(project)
if proj is None:
module.fail_json(msg='Project %s could not be found' % project)
self.fail(msg='Project %s could not be found' % project)
project_id = proj['id']
filters = {'tenant_id': project_id}
else:
project_id = None
filters = None
router = cloud.get_router(name, filters=filters)
router = self.conn.get_router(name, filters=filters)
net = None
if network:
net = cloud.get_network(network)
net = self.conn.get_network(network)
if not net:
module.fail_json(msg='network %s not found' % network)
self.fail(msg='network %s not found' % network)
# Validate and cache the subnet IDs so we can avoid duplicate checks
# and expensive API calls.
external_ids, subnet_internal_ids, internal_portids = _validate_subnets(module, cloud, filters)
if module.check_mode:
module.exit_json(
changed=_system_state_change(cloud, module, router, net, subnet_internal_ids, internal_portids, filters)
external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters)
if self.ansible.check_mode:
self.exit_json(
changed=self._system_state_change(router, net, subnet_internal_ids, internal_portids, filters)
)
if state == 'present':
changed = False
if not router:
kwargs = _build_kwargs(cloud, module, router, net)
kwargs = self._build_kwargs(router, net)
if project_id:
kwargs['project_id'] = project_id
router = cloud.create_router(**kwargs)
router = self.conn.create_router(**kwargs)
for int_s_id in subnet_internal_ids:
cloud.add_router_interface(router, subnet_id=int_s_id)
changed = True
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:
cloud.add_router_interface(router, port_id=int_p_id)
self.conn.add_router_interface(router, port_id=int_p_id)
changed = True
else:
if _needs_update(cloud, module, router, net, subnet_internal_ids, internal_portids, filters):
kwargs = _build_kwargs(cloud, module, router, net)
updated_router = cloud.update_router(**kwargs)
if self._needs_update(router, net, subnet_internal_ids, internal_portids, filters):
kwargs = self._build_kwargs(router, net)
updated_router = self.conn.update_router(**kwargs)
# Protect against update_router() not actually
# updating the router.
@@ -455,38 +446,38 @@ def main():
# just detach all existing internal interfaces and attach the new.
if internal_portids or subnet_internal_ids:
router = updated_router
ports = _router_internal_interfaces(cloud, router)
ports = self._router_internal_interfaces(router)
for port in ports:
cloud.remove_router_interface(router, port_id=port['id'])
self.conn.remove_router_interface(router, port_id=port['id'])
if internal_portids:
external_ids, subnet_internal_ids, internal_portids = _validate_subnets(module, cloud, filters)
external_ids, subnet_internal_ids, internal_portids = self._validate_subnets(filters)
for int_p_id in internal_portids:
cloud.add_router_interface(router, port_id=int_p_id)
self.conn.add_router_interface(router, port_id=int_p_id)
changed = True
if subnet_internal_ids:
for s_id in subnet_internal_ids:
cloud.add_router_interface(router, subnet_id=s_id)
self.conn.add_router_interface(router, subnet_id=s_id)
changed = True
module.exit_json(changed=changed,
router=router,
id=router['id'])
self.exit(changed=changed, router=router, id=router['id'])
elif state == 'absent':
if not router:
module.exit_json(changed=False)
self.exit(changed=False)
else:
# We need to detach all internal interfaces on a router before
# we will be allowed to delete it.
ports = _router_internal_interfaces(cloud, router)
ports = self._router_internal_interfaces(router)
router_id = router['id']
for port in ports:
cloud.remove_router_interface(router, port_id=port['id'])
cloud.delete_router(router_id)
module.exit_json(changed=True)
self.conn.remove_router_interface(router, port_id=port['id'])
self.conn.delete_router(router_id)
self.exit_json(changed=True)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
def main():
module = RouterModule()
module()
if __name__ == '__main__':

View File

@@ -144,25 +144,30 @@ openstack_routers:
type: list
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, openstack_cloud_from_module
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def main():
class RouterInfoModule(OpenStackModule):
argument_spec = openstack_full_argument_spec(
deprecated_names = ('os_routers_info', 'openstack.cloud.os_routers_info')
argument_spec = dict(
name=dict(required=False, default=None),
filters=dict(required=False, type='dict', default=None)
)
module = AnsibleModule(argument_spec)
sdk, cloud = openstack_cloud_from_module(module)
try:
routers = cloud.search_routers(module.params['name'],
module.params['filters'])
def run(self):
kwargs = self.check_versioned(
filters=self.params['filters']
)
if self.params['name']:
kwargs['name_or_id'] = self.params['name']
routers = self.conn.search_routers(**kwargs)
for router in routers:
interfaces_info = []
for port in cloud.list_router_interfaces(router):
for port in self.conn.list_router_interfaces(router):
if port.device_owner != "network:router_gateway":
for ip_spec in port.fixed_ips:
int_info = {
@@ -173,10 +178,12 @@ def main():
interfaces_info.append(int_info)
router['interfaces_info'] = interfaces_info
module.exit_json(changed=False, openstack_routers=routers)
self.exit(changed=False, openstack_routers=routers)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
def main():
module = RouterInfoModule()
module()
if __name__ == '__main__':

View File

@@ -64,70 +64,61 @@ EXAMPLES = '''
project: myproj
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def _needs_update(module, secgroup):
"""Check for differences in the updatable values.
class SecurityGroupModule(OpenStackModule):
NOTE: We don't currently allow name updates.
"""
if secgroup['description'] != module.params['description']:
return True
return False
def _system_state_change(module, secgroup):
state = module.params['state']
if state == 'present':
if not secgroup:
return True
return _needs_update(module, secgroup)
if state == 'absent' and secgroup:
return True
return False
def main():
argument_spec = openstack_full_argument_spec(
argument_spec = dict(
name=dict(required=True),
description=dict(default=''),
state=dict(default='present', choices=['absent', 'present']),
project=dict(default=None),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
def _needs_update(self, secgroup):
"""Check for differences in the updatable values.
name = module.params['name']
state = module.params['state']
description = module.params['description']
project = module.params['project']
NOTE: We don't currently allow name updates.
"""
if secgroup['description'] != self.params['description']:
return True
return False
def _system_state_change(self, secgroup):
state = self.params['state']
if state == 'present':
if not secgroup:
return True
return self._needs_update(secgroup)
if state == 'absent' and secgroup:
return True
return False
def run(self):
name = self.params['name']
state = self.params['state']
description = self.params['description']
project = self.params['project']
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
proj = self.conn.get_project(project)
if proj is None:
module.fail_json(msg='Project %s could not be found' % project)
self.fail_json(msg='Project %s could not be found' % project)
project_id = proj['id']
else:
project_id = cloud.current_project_id
project_id = self.conn.current_project_id
if project_id:
filters = {'tenant_id': project_id}
else:
filters = None
secgroup = cloud.get_security_group(name, filters=filters)
secgroup = self.conn.get_security_group(name, filters=filters)
if module.check_mode:
module.exit_json(changed=_system_state_change(module, secgroup))
if self.ansible.check_mode:
self.exit(changed=self._system_state_change(secgroup))
changed = False
if state == 'present':
@@ -135,26 +126,28 @@ def main():
kwargs = {}
if project_id:
kwargs['project_id'] = project_id
secgroup = cloud.create_security_group(name, description,
**kwargs)
secgroup = self.conn.create_security_group(name, description,
**kwargs)
changed = True
else:
if _needs_update(module, secgroup):
secgroup = cloud.update_security_group(
if self._needs_update(secgroup):
secgroup = self.conn.update_security_group(
secgroup['id'], description=description)
changed = True
module.exit_json(
self.exit(
changed=changed, id=secgroup['id'], secgroup=secgroup)
if state == 'absent':
if secgroup:
cloud.delete_security_group(secgroup['id'])
self.conn.delete_security_group(secgroup['id'])
changed = True
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
self.exit(changed=changed)
if __name__ == "__main__":
def main():
module = SecurityGroupModule()
module()
if __name__ == '__main__':
main()

View File

@@ -213,21 +213,20 @@ def _ports_match(protocol, module_min, module_max, rule_min, rule_max):
if protocol == 'any':
return True
# Check if the user is supplying -1 or None values for full TPC/UDP port range.
# Check if the user is supplying -1, 1 to 65535 or None values for full TPC/UDP port range.
if protocol in ['tcp', 'udp'] or protocol is None:
if module_min and module_max and int(module_min) == int(module_max) == -1:
module_min = None
module_max = None
if (
(module_min is None and module_max is None)
and (
rule_min and int(rule_min) == 1
and rule_max and int(rule_max) == 65535
)
not module_min and not module_max
or (int(module_min) in [-1, 1]
and int(module_max) in [-1, 65535])
):
# (None, None) == (1, 65535)
return True
if (
not rule_min and not rule_max
or (int(rule_min) in [-1, 1]
and int(rule_max) in [-1, 65535])
):
# (None, None) == (1, 65535) == (-1, -1)
return True
# Sanity check to make sure we don't have type comparison issues.
if module_min:

View File

@@ -464,48 +464,6 @@ def _parse_nics(nics):
yield net
def _network_args(module, cloud):
args = []
nics = module.params['nics']
if not isinstance(nics, list):
module.fail(msg='The \'nics\' parameter must be a list.')
for num, net in enumerate(_parse_nics(nics)):
if not isinstance(net, dict):
module.fail(
msg='Each entry in the \'nics\' parameter must be a dict.')
if net.get('net-id'):
args.append(net)
elif net.get('net-name'):
by_name = cloud.get_network(net['net-name'])
if not by_name:
module.fail(
msg='Could not find network by net-name: %s' %
net['net-name'])
resolved_net = net.copy()
del resolved_net['net-name']
resolved_net['net-id'] = by_name['id']
args.append(resolved_net)
elif net.get('port-id'):
args.append(net)
elif net.get('port-name'):
by_name = cloud.get_port(net['port-name'])
if not by_name:
module.fail(
msg='Could not find port by port-name: %s' %
net['port-name'])
resolved_net = net.copy()
del resolved_net['port-name']
resolved_net['port-id'] = by_name['id']
args.append(resolved_net)
if 'tag' in net:
args[num]['tag'] = net['tag']
return args
def _parse_meta(meta):
if isinstance(meta, str):
metas = {}
@@ -518,101 +476,6 @@ def _parse_meta(meta):
return meta
def _detach_ip_list(cloud, server, extra_ips):
for ip in extra_ips:
ip_id = cloud.get_floating_ip(
id=None, filters={'floating_ip_address': ip})
cloud.detach_ip_from_server(
server_id=server.id, floating_ip_id=ip_id)
def _check_ips(module, cloud, server):
changed = False
auto_ip = module.params['auto_ip']
floating_ips = module.params['floating_ips']
floating_ip_pools = module.params['floating_ip_pools']
if floating_ip_pools or floating_ips:
ips = openstack_find_nova_addresses(server.addresses, 'floating')
if not ips:
# If we're configured to have a floating but we don't have one,
# let's add one
server = cloud.add_ips_to_server(
server,
auto_ip=auto_ip,
ips=floating_ips,
ip_pool=floating_ip_pools,
wait=module.params['wait'],
timeout=module.params['timeout'],
)
changed = True
elif floating_ips:
# we were configured to have specific ips, let's make sure we have
# those
missing_ips = []
for ip in floating_ips:
if ip not in ips:
missing_ips.append(ip)
if missing_ips:
server = cloud.add_ip_list(server, missing_ips,
wait=module.params['wait'],
timeout=module.params['timeout'])
changed = True
extra_ips = []
for ip in ips:
if ip not in floating_ips:
extra_ips.append(ip)
if extra_ips:
_detach_ip_list(cloud, server, extra_ips)
changed = True
elif auto_ip:
if server['interface_ip']:
changed = False
else:
# We're configured for auto_ip but we're not showing an
# interface_ip. Maybe someone deleted an IP out from under us.
server = cloud.add_ips_to_server(
server,
auto_ip=auto_ip,
ips=floating_ips,
ip_pool=floating_ip_pools,
wait=module.params['wait'],
timeout=module.params['timeout'],
)
changed = True
return (changed, server)
def _check_security_groups(module, cloud, server):
changed = False
# server security groups were added to shade in 1.19. Until then this
# module simply ignored trying to update security groups and only set them
# on newly created hosts.
if not (
hasattr(cloud, 'add_server_security_groups')
and hasattr(cloud, 'remove_server_security_groups')
):
return changed, server
module_security_groups = set(module.params['security_groups'])
server_security_groups = set(sg['name'] for sg in server.security_groups)
add_sgs = module_security_groups - server_security_groups
remove_sgs = server_security_groups - module_security_groups
if add_sgs:
cloud.add_server_security_groups(server, list(add_sgs))
changed = True
if remove_sgs:
cloud.remove_server_security_groups(server, list(remove_sgs))
changed = True
return (changed, server)
class ServerModule(OpenStackModule):
deprecated_names = ('os_server', 'openstack.cloud.os_server')
@@ -697,8 +560,8 @@ class ServerModule(OpenStackModule):
if server.status not in ('ACTIVE', 'SHUTOFF', 'PAUSED', 'SUSPENDED'):
self.fail(
msg="The instance is available but not Active state: " + server.status)
(ip_changed, server) = _check_ips(self, self.conn, server)
(sg_changed, server) = _check_security_groups(self, self.conn, server)
(ip_changed, server) = self._check_ips(server)
(sg_changed, server) = self._check_security_groups(server)
(server_changed, server) = self._update_server(server)
self._exit_hostvars(server, ip_changed or sg_changed or server_changed)
if server and state == 'absent':
@@ -729,7 +592,7 @@ class ServerModule(OpenStackModule):
if not flavor_dict:
self.fail(msg="Could not find any matching flavor")
nics = _network_args(self, self.conn)
nics = self._network_args()
self.params['meta'] = _parse_meta(self.params['meta'])
@@ -768,7 +631,7 @@ class ServerModule(OpenStackModule):
self.params['meta'] = _parse_meta(self.params['meta'])
# cloud.set_server_metadata only updates the key=value pairs, it doesn't
# self.conn.set_server_metadata only updates the key=value pairs, it doesn't
# touch existing ones
update_meta = {}
for (k, v) in self.params['meta'].items():
@@ -793,6 +656,139 @@ class ServerModule(OpenStackModule):
self.fail(msg="Error in deleting vm: %s" % e)
self.exit(changed=True, result='deleted')
def _network_args(self):
args = []
nics = self.params['nics']
if not isinstance(nics, list):
self.fail(msg='The \'nics\' parameter must be a list.')
for num, net in enumerate(_parse_nics(nics)):
if not isinstance(net, dict):
self.fail(
msg='Each entry in the \'nics\' parameter must be a dict.')
if net.get('net-id'):
args.append(net)
elif net.get('net-name'):
by_name = self.conn.get_network(net['net-name'])
if not by_name:
self.fail(
msg='Could not find network by net-name: %s' %
net['net-name'])
resolved_net = net.copy()
del resolved_net['net-name']
resolved_net['net-id'] = by_name['id']
args.append(resolved_net)
elif net.get('port-id'):
args.append(net)
elif net.get('port-name'):
by_name = self.conn.get_port(net['port-name'])
if not by_name:
self.fail(
msg='Could not find port by port-name: %s' %
net['port-name'])
resolved_net = net.copy()
del resolved_net['port-name']
resolved_net['port-id'] = by_name['id']
args.append(resolved_net)
if 'tag' in net:
args[num]['tag'] = net['tag']
return args
def _detach_ip_list(self, server, extra_ips):
for ip in extra_ips:
ip_id = self.conn.get_floating_ip(
id=None, filters={'floating_ip_address': ip})
self.conn.detach_ip_from_server(
server_id=server.id, floating_ip_id=ip_id)
def _check_ips(self, server):
changed = False
auto_ip = self.params['auto_ip']
floating_ips = self.params['floating_ips']
floating_ip_pools = self.params['floating_ip_pools']
if floating_ip_pools or floating_ips:
ips = openstack_find_nova_addresses(server.addresses, 'floating')
if not ips:
# If we're configured to have a floating but we don't have one,
# let's add one
server = self.conn.add_ips_to_server(
server,
auto_ip=auto_ip,
ips=floating_ips,
ip_pool=floating_ip_pools,
wait=self.params['wait'],
timeout=self.params['timeout'],
)
changed = True
elif floating_ips:
# we were configured to have specific ips, let's make sure we have
# those
missing_ips = []
for ip in floating_ips:
if ip not in ips:
missing_ips.append(ip)
if missing_ips:
server = self.conn.add_ip_list(server, missing_ips,
wait=self.params['wait'],
timeout=self.params['timeout'])
changed = True
extra_ips = []
for ip in ips:
if ip not in floating_ips:
extra_ips.append(ip)
if extra_ips:
self._detach_ip_list(server, extra_ips)
changed = True
elif auto_ip:
if server['interface_ip']:
changed = False
else:
# We're configured for auto_ip but we're not showing an
# interface_ip. Maybe someone deleted an IP out from under us.
server = self.conn.add_ips_to_server(
server,
auto_ip=auto_ip,
ips=floating_ips,
ip_pool=floating_ip_pools,
wait=self.params['wait'],
timeout=self.params['timeout'],
)
changed = True
return (changed, server)
def _check_security_groups(self, server):
changed = False
# server security groups were added to shade in 1.19. Until then this
# module simply ignored trying to update security groups and only set them
# on newly created hosts.
if not (
hasattr(self.conn, 'add_server_security_groups')
and hasattr(self.conn, 'remove_server_security_groups')
):
return changed, server
module_security_groups = set(self.params['security_groups'])
server_security_groups = set(sg['name'] for sg in server.security_groups)
add_sgs = module_security_groups - server_security_groups
remove_sgs = server_security_groups - module_security_groups
if add_sgs:
self.conn.add_server_security_groups(server, list(add_sgs))
changed = True
if remove_sgs:
self.conn.remove_server_security_groups(server, list(remove_sgs))
changed = True
return (changed, server)
def main():
module = ServerModule()

View File

@@ -55,10 +55,7 @@ EXAMPLES = '''
device: /dev/vdb
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def _system_state_change(state, device):
@@ -74,48 +71,44 @@ def _system_state_change(state, device):
return False
def main():
argument_spec = openstack_full_argument_spec(
class ServerVolumeModule(OpenStackModule):
argument_spec = dict(
server=dict(required=True),
volume=dict(required=True),
device=dict(default=None), # None == auto choose device name
state=dict(default='present', choices=['absent', 'present']),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
def run(self):
state = module.params['state']
wait = module.params['wait']
timeout = module.params['timeout']
state = self.params['state']
wait = self.params['wait']
timeout = self.params['timeout']
sdk, cloud = openstack_cloud_from_module(module)
try:
server = cloud.get_server(module.params['server'])
volume = cloud.get_volume(module.params['volume'])
server = self.conn.get_server(self.params['server'])
volume = self.conn.get_volume(self.params['volume'])
if not volume:
module.fail_json(msg='volume %s is not found' % module.params['volume'])
self.fail(msg='volume %s is not found' % self.params['volume'])
dev = cloud.get_volume_attach_device(volume, server.id)
dev = self.conn.get_volume_attach_device(volume, server.id)
if module.check_mode:
module.exit_json(changed=_system_state_change(state, dev))
if self.ansible.check_mode:
self.exit(changed=_system_state_change(state, dev))
if state == 'present':
changed = False
if not dev:
changed = True
cloud.attach_volume(server, volume, module.params['device'],
wait=wait, timeout=timeout)
self.conn.attach_volume(server, volume, self.params['device'],
wait=wait, timeout=timeout)
server = cloud.get_server(module.params['server']) # refresh
volume = cloud.get_volume(module.params['volume']) # refresh
hostvars = cloud.get_openstack_vars(server)
server = self.conn.get_server(self.params['server']) # refresh
volume = self.conn.get_volume(self.params['volume']) # refresh
hostvars = self.conn.get_openstack_vars(server)
module.exit_json(
self.exit(
changed=changed,
id=volume['id'],
attachments=volume['attachments'],
@@ -125,16 +118,18 @@ def main():
elif state == 'absent':
if not dev:
# Volume is not attached to this server
module.exit_json(changed=False)
self.exit(changed=False)
cloud.detach_volume(server, volume, wait=wait, timeout=timeout)
module.exit_json(
self.conn.detach_volume(server, volume, wait=wait, timeout=timeout)
self.exit(
changed=True,
result='Detached volume from server'
)
except (sdk.exceptions.OpenStackCloudException, sdk.exceptions.ResourceTimeout) as e:
module.fail_json(msg=str(e))
def main():
module = ServerVolumeModule()
module()
if __name__ == '__main__':

View File

@@ -153,90 +153,12 @@ EXAMPLES = '''
ipv6_address_mode: dhcpv6-stateless
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def _can_update(subnet, module, cloud, filters=None):
"""Check for differences in non-updatable values"""
network_name = module.params['network_name']
ip_version = int(module.params['ip_version'])
ipv6_ra_mode = module.params['ipv6_ra_mode']
ipv6_a_mode = module.params['ipv6_address_mode']
if network_name:
network = cloud.get_network(network_name, filters)
if network:
netid = network['id']
else:
module.fail_json(msg='No network found for %s' % network_name)
if netid != subnet['network_id']:
module.fail_json(msg='Cannot update network_name in existing \
subnet')
if ip_version and subnet['ip_version'] != ip_version:
module.fail_json(msg='Cannot update ip_version in existing subnet')
if ipv6_ra_mode and subnet.get('ipv6_ra_mode', None) != ipv6_ra_mode:
module.fail_json(msg='Cannot update ipv6_ra_mode in existing subnet')
if ipv6_a_mode and subnet.get('ipv6_address_mode', None) != ipv6_a_mode:
module.fail_json(msg='Cannot update ipv6_address_mode in existing \
subnet')
def _needs_update(subnet, module, cloud, filters=None):
"""Check for differences in the updatable values."""
# First check if we are trying to update something we're not allowed to
_can_update(subnet, module, cloud, filters)
# now check for the things we are allowed to update
enable_dhcp = module.params['enable_dhcp']
subnet_name = module.params['name']
pool_start = module.params['allocation_pool_start']
pool_end = module.params['allocation_pool_end']
gateway_ip = module.params['gateway_ip']
no_gateway_ip = module.params['no_gateway_ip']
dns = module.params['dns_nameservers']
host_routes = module.params['host_routes']
curr_pool = dict(start=pool_start, end=pool_end)
if subnet['enable_dhcp'] != enable_dhcp:
return True
if subnet_name and subnet['name'] != subnet_name:
return True
if not subnet['allocation_pools'] and pool_start and pool_end:
return True
if subnet['allocation_pools'] and curr_pool not in subnet['allocation_pools']:
return True
if gateway_ip and subnet['gateway_ip'] != gateway_ip:
return True
if dns and sorted(subnet['dns_nameservers']) != sorted(dns):
return True
if host_routes:
curr_hr = sorted(subnet['host_routes'], key=lambda t: t.keys())
new_hr = sorted(host_routes, key=lambda t: t.keys())
if curr_hr != new_hr:
return True
if no_gateway_ip and subnet['gateway_ip']:
return True
return False
def _system_state_change(module, subnet, cloud, filters=None):
state = module.params['state']
if state == 'present':
if not subnet:
return True
return _needs_update(subnet, module, cloud, filters)
if state == 'absent' and subnet:
return True
return False
def main():
class SubnetModule(OpenStackModule):
ipv6_mode_choices = ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac']
argument_spec = openstack_full_argument_spec(
argument_spec = dict(
name=dict(type='str', required=True),
network_name=dict(type='str'),
cidr=dict(type='str'),
@@ -256,69 +178,136 @@ def main():
project=dict(type='str'),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
required_together=[
['allocation_pool_end', 'allocation_pool_start'],
],
**module_kwargs)
module_kwargs = dict(
supports_check_mode=True,
required_together=[['allocation_pool_end', 'allocation_pool_start']]
)
state = module.params['state']
network_name = module.params['network_name']
cidr = module.params['cidr']
ip_version = module.params['ip_version']
enable_dhcp = module.params['enable_dhcp']
subnet_name = module.params['name']
gateway_ip = module.params['gateway_ip']
no_gateway_ip = module.params['no_gateway_ip']
dns = module.params['dns_nameservers']
pool_start = module.params['allocation_pool_start']
pool_end = module.params['allocation_pool_end']
host_routes = module.params['host_routes']
ipv6_ra_mode = module.params['ipv6_ra_mode']
ipv6_a_mode = module.params['ipv6_address_mode']
use_default_subnetpool = module.params['use_default_subnetpool']
project = module.params.pop('project')
extra_specs = module.params['extra_specs']
def _can_update(self, subnet, filters=None):
"""Check for differences in non-updatable values"""
network_name = self.params['network_name']
ip_version = int(self.params['ip_version'])
ipv6_ra_mode = self.params['ipv6_ra_mode']
ipv6_a_mode = self.params['ipv6_address_mode']
# Check for required parameters when state == 'present'
if state == 'present':
if not module.params['network_name']:
module.fail_json(msg='network_name required with present state')
if (
not module.params['cidr']
and not use_default_subnetpool
and not extra_specs.get('subnetpool_id', False)
):
module.fail_json(msg='cidr or use_default_subnetpool or '
'subnetpool_id required with present state')
if network_name:
network = self.conn.get_network(network_name, filters)
if network:
netid = network['id']
if netid != subnet['network_id']:
self.fail_json(msg='Cannot update network_name in existing subnet')
else:
self.fail_json(msg='No network found for %s' % network_name)
if pool_start and pool_end:
pool = [dict(start=pool_start, end=pool_end)]
else:
pool = None
if ip_version and subnet['ip_version'] != ip_version:
self.fail_json(msg='Cannot update ip_version in existing subnet')
if ipv6_ra_mode and subnet.get('ipv6_ra_mode', None) != ipv6_ra_mode:
self.fail_json(msg='Cannot update ipv6_ra_mode in existing subnet')
if ipv6_a_mode and subnet.get('ipv6_address_mode', None) != ipv6_a_mode:
self.fail_json(msg='Cannot update ipv6_address_mode in existing subnet')
if no_gateway_ip and gateway_ip:
module.fail_json(msg='no_gateway_ip is not allowed with gateway_ip')
def _needs_update(self, subnet, filters=None):
"""Check for differences in the updatable values."""
# First check if we are trying to update something we're not allowed to
self._can_update(subnet, filters)
# now check for the things we are allowed to update
enable_dhcp = self.params['enable_dhcp']
subnet_name = self.params['name']
pool_start = self.params['allocation_pool_start']
pool_end = self.params['allocation_pool_end']
gateway_ip = self.params['gateway_ip']
no_gateway_ip = self.params['no_gateway_ip']
dns = self.params['dns_nameservers']
host_routes = self.params['host_routes']
curr_pool = dict(start=pool_start, end=pool_end)
if subnet['enable_dhcp'] != enable_dhcp:
return True
if subnet_name and subnet['name'] != subnet_name:
return True
if not subnet['allocation_pools'] and pool_start and pool_end:
return True
if subnet['allocation_pools'] != [curr_pool]:
return True
if gateway_ip and subnet['gateway_ip'] != gateway_ip:
return True
if dns and sorted(subnet['dns_nameservers']) != sorted(dns):
return True
if host_routes:
curr_hr = sorted(subnet['host_routes'], key=lambda t: t.keys())
new_hr = sorted(host_routes, key=lambda t: t.keys())
if curr_hr != new_hr:
return True
if no_gateway_ip and subnet['gateway_ip']:
return True
return False
def _system_state_change(self, subnet, filters=None):
state = self.params['state']
if state == 'present':
if not subnet:
return True
return self._needs_update(subnet, filters)
if state == 'absent' and subnet:
return True
return False
def run(self):
state = self.params['state']
network_name = self.params['network_name']
cidr = self.params['cidr']
ip_version = self.params['ip_version']
enable_dhcp = self.params['enable_dhcp']
subnet_name = self.params['name']
gateway_ip = self.params['gateway_ip']
no_gateway_ip = self.params['no_gateway_ip']
dns = self.params['dns_nameservers']
pool_start = self.params['allocation_pool_start']
pool_end = self.params['allocation_pool_end']
host_routes = self.params['host_routes']
ipv6_ra_mode = self.params['ipv6_ra_mode']
ipv6_a_mode = self.params['ipv6_address_mode']
use_default_subnetpool = self.params['use_default_subnetpool']
project = self.params.pop('project')
extra_specs = self.params['extra_specs']
# Check for required parameters when state == 'present'
if state == 'present':
if not self.params['network_name']:
self.fail(msg='network_name required with present state')
if (
not self.params['cidr']
and not use_default_subnetpool
and not extra_specs.get('subnetpool_id', False)
):
self.fail(msg='cidr or use_default_subnetpool or '
'subnetpool_id required with present state')
if pool_start and pool_end:
pool = [dict(start=pool_start, end=pool_end)]
else:
pool = None
if no_gateway_ip and gateway_ip:
self.fail_json(msg='no_gateway_ip is not allowed with gateway_ip')
sdk, cloud = openstack_cloud_from_module(module)
try:
if project is not None:
proj = cloud.get_project(project)
proj = self.conn.get_project(project)
if proj is None:
module.fail_json(msg='Project %s could not be found' % project)
self.fail_json(msg='Project %s could not be found' % project)
project_id = proj['id']
filters = {'tenant_id': project_id}
else:
project_id = None
filters = None
subnet = cloud.get_subnet(subnet_name, filters=filters)
subnet = self.conn.get_subnet(subnet_name, filters=filters)
if module.check_mode:
module.exit_json(changed=_system_state_change(module, subnet,
cloud, filters))
if self.ansible.check_mode:
self.exit_json(changed=self._system_state_change(subnet, filters))
if state == 'present':
if not subnet:
@@ -342,37 +331,37 @@ def main():
if use_default_subnetpool:
kwargs['use_default_subnetpool'] = use_default_subnetpool
kwargs = dict(kwargs, **extra_specs)
subnet = cloud.create_subnet(network_name, **kwargs)
subnet = self.conn.create_subnet(network_name, **kwargs)
changed = True
else:
if _needs_update(subnet, module, cloud, filters):
if subnet['allocation_pools'] and pool is not None:
pool = pool + subnet['allocation_pools']
cloud.update_subnet(subnet['id'],
subnet_name=subnet_name,
enable_dhcp=enable_dhcp,
gateway_ip=gateway_ip,
disable_gateway_ip=no_gateway_ip,
dns_nameservers=dns,
allocation_pools=pool,
host_routes=host_routes)
if self._needs_update(subnet, filters):
subnet = self.conn.update_subnet(subnet['id'],
subnet_name=subnet_name,
enable_dhcp=enable_dhcp,
gateway_ip=gateway_ip,
disable_gateway_ip=no_gateway_ip,
dns_nameservers=dns,
allocation_pools=pool,
host_routes=host_routes)
changed = True
else:
changed = False
module.exit_json(changed=changed,
subnet=subnet,
id=subnet['id'])
self.exit_json(changed=changed,
subnet=subnet,
id=subnet['id'])
elif state == 'absent':
if not subnet:
changed = False
else:
changed = True
cloud.delete_subnet(subnet_name)
module.exit_json(changed=changed)
self.conn.delete_subnet(subnet_name)
self.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
def main():
module = SubnetModule()
module()
if __name__ == '__main__':

View File

@@ -129,34 +129,32 @@ openstack_subnets:
elements: dict
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec, openstack_cloud_from_module
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def main():
class SubnetInfoModule(OpenStackModule):
argument_spec = openstack_full_argument_spec(
deprecated_names = ('subnets_facts', 'openstack.cloud.subnets_facts')
argument_spec = dict(
name=dict(required=False, default=None, aliases=['subnet']),
filters=dict(required=False, type='dict', default=None)
)
module = AnsibleModule(argument_spec)
is_old_facts = module._name == 'openstack.cloud.subnets_facts'
if is_old_facts:
module.deprecate("The 'openstack.cloud.subnets_facts' module has been renamed to 'openstack.cloud.subnets_info', "
"and the renamed one no longer returns ansible_facts", version='2.13')
sdk, cloud = openstack_cloud_from_module(module)
try:
subnets = cloud.search_subnets(module.params['name'],
module.params['filters'])
if is_old_facts:
module.exit_json(changed=False, ansible_facts=dict(
openstack_subnets=subnets))
else:
module.exit_json(changed=False, openstack_subnets=subnets)
def run(self):
kwargs = self.check_versioned(
filters=self.params['filters']
)
if self.params['name']:
kwargs['name_or_id'] = self.params['name']
subnets = self.conn.search_subnets(**kwargs)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
self.exit(changed=False, openstack_subnets=subnets)
def main():
module = SubnetInfoModule()
module()
if __name__ == '__main__':

View File

@@ -99,161 +99,162 @@ volume:
type: dict
sample: {'...'}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
def _needs_update(module, volume):
'''
check for differences in updatable values, at the moment
openstacksdk only supports extending the volume size, this
may change in the future.
:returns: bool
'''
compare_simple = ['size']
class VolumeModule(OpenStackModule):
for k in compare_simple:
if module.params[k] is not None and module.params[k] != volume.get(k):
return True
return False
def _modify_volume(module, cloud):
'''
modify volume, the only modification to an existing volume
available at the moment is extending the size, this is
limited by the openstacksdk and may change whenever the
functionality is extended.
'''
volume = cloud.get_volume(module.params['display_name'])
diff = {'before': volume, 'after': ''}
size = module.params['size']
if size < volume.get('size'):
module.fail_json(
msg='Cannot shrink volumes, size: {0} < {1}'.format(size, volume.get('size'))
)
if not _needs_update(module, volume):
diff['after'] = volume
module.exit_json(changed=False, id=volume['id'], volume=volume, diff=diff)
if module.check_mode:
diff['after'] = volume
module.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
cloud.volume.extend_volume(
volume.id,
size
)
diff['after'] = cloud.get_volume(module.params['display_name'])
module.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
def _present_volume(module, cloud):
if cloud.volume_exists(module.params['display_name']):
v = cloud.get_volume(module.params['display_name'])
if not _needs_update(module, v):
module.exit_json(changed=False, id=v['id'], volume=v)
_modify_volume(module, cloud)
diff = {'before': '', 'after': ''}
volume_args = dict(
size=module.params['size'],
volume_type=module.params['volume_type'],
display_name=module.params['display_name'],
display_description=module.params['display_description'],
snapshot_id=module.params['snapshot_id'],
bootable=module.params['bootable'],
availability_zone=module.params['availability_zone'],
)
if module.params['image']:
image_id = cloud.get_image_id(module.params['image'])
volume_args['imageRef'] = image_id
if module.params['volume']:
volume_id = cloud.get_volume_id(module.params['volume'])
if not volume_id:
module.fail_json(msg="Failed to find volume '%s'" % module.params['volume'])
volume_args['source_volid'] = volume_id
if module.params['scheduler_hints']:
volume_args['scheduler_hints'] = module.params['scheduler_hints']
if module.params['metadata']:
volume_args['metadata'] = module.params['metadata']
if module.check_mode:
diff['after'] = volume_args
module.exit_json(changed=True, id=None, volume=volume_args, diff=diff)
volume = cloud.create_volume(
wait=module.params['wait'], timeout=module.params['timeout'],
**volume_args)
diff['after'] = volume
module.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
def _absent_volume(module, cloud, sdk):
changed = False
diff = {'before': '', 'after': ''}
if cloud.volume_exists(module.params['display_name']):
volume = cloud.get_volume(module.params['display_name'])
diff['before'] = volume
if module.check_mode:
module.exit_json(changed=True, diff=diff)
try:
changed = cloud.delete_volume(name_or_id=module.params['display_name'],
wait=module.params['wait'],
timeout=module.params['timeout'])
except sdk.exceptions.ResourceTimeout:
diff['after'] = volume
module.exit_json(changed=changed, diff=diff)
module.exit_json(changed=changed, diff=diff)
def main():
argument_spec = openstack_full_argument_spec(
size=dict(default=None, type='int'),
volume_type=dict(default=None),
display_name=dict(required=True, aliases=['name']),
display_description=dict(default=None, aliases=['description']),
image=dict(default=None),
snapshot_id=dict(default=None),
volume=dict(default=None),
state=dict(default='present', choices=['absent', 'present']),
scheduler_hints=dict(default=None, type='dict'),
metadata=dict(default=None, type='dict'),
argument_spec = dict(
size=dict(type='int'),
volume_type=dict(type='str'),
display_name=dict(required=True, aliases=['name'], type='str'),
display_description=dict(aliases=['description'], type='str'),
image=dict(type='str'),
snapshot_id=dict(type='str'),
volume=dict(type='str'),
state=dict(default='present', choices=['absent', 'present'], type='str'),
scheduler_hints=dict(type='dict'),
metadata=dict(type='dict'),
bootable=dict(type='bool', default=False)
)
module_kwargs = openstack_module_kwargs(
module_kwargs = dict(
mutually_exclusive=[
['image', 'snapshot_id', 'volume'],
],
required_if=[
['state', 'present', ['size']],
],
)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, **module_kwargs)
state = module.params['state']
def _needs_update(self, volume):
'''
check for differences in updatable values, at the moment
openstacksdk only supports extending the volume size, this
may change in the future.
:returns: bool
'''
compare_simple = ['size']
if state == 'present' and not module.params['size']:
module.fail_json(msg="Size is required when state is 'present'")
for k in compare_simple:
if self.params[k] is not None and self.params[k] != volume.get(k):
return True
return False
def _modify_volume(self, volume):
'''
modify volume, the only modification to an existing volume
available at the moment is extending the size, this is
limited by the openstacksdk and may change whenever the
functionality is extended.
'''
volume = self.conn.get_volume(self.params['display_name'])
diff = {'before': volume, 'after': ''}
size = self.params['size']
if size < volume.get('size'):
self.fail_json(
msg='Cannot shrink volumes, size: {0} < {1}'.format(size, volume.get('size'))
)
if not self._needs_update(volume):
diff['after'] = volume
self.exit_json(changed=False, id=volume['id'], volume=volume, diff=diff)
if self.ansible.check_mode:
diff['after'] = volume
self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
self.conn.volume.extend_volume(
volume.id,
size
)
diff['after'] = self.conn.get_volume(self.params['display_name'])
self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
def _present_volume(self):
diff = {'before': '', 'after': ''}
volume_args = dict(
size=self.params['size'],
volume_type=self.params['volume_type'],
display_name=self.params['display_name'],
display_description=self.params['display_description'],
snapshot_id=self.params['snapshot_id'],
bootable=self.params['bootable'],
availability_zone=self.params['availability_zone'],
)
if self.params['image']:
image_id = self.conn.get_image_id(self.params['image'])
volume_args['imageRef'] = image_id
if self.params['volume']:
volume_id = self.conn.get_volume_id(self.params['volume'])
if not volume_id:
self.fail_json(msg="Failed to find volume '%s'" % self.params['volume'])
volume_args['source_volid'] = volume_id
if self.params['scheduler_hints']:
volume_args['scheduler_hints'] = self.params['scheduler_hints']
if self.params['metadata']:
volume_args['metadata'] = self.params['metadata']
if self.ansible.check_mode:
diff['after'] = volume_args
self.exit_json(changed=True, id=None, volume=volume_args, diff=diff)
volume = self.conn.create_volume(
wait=self.params['wait'], timeout=self.params['timeout'],
**volume_args)
diff['after'] = volume
self.exit_json(changed=True, id=volume['id'], volume=volume, diff=diff)
def _absent_volume(self, volume):
changed = False
diff = {'before': '', 'after': ''}
if self.conn.volume_exists(self.params['display_name']):
volume = self.conn.get_volume(self.params['display_name'])
diff['before'] = volume
if self.ansible.check_mode:
self.exit_json(changed=True, diff=diff)
try:
changed = self.conn.delete_volume(name_or_id=self.params['display_name'],
wait=self.params['wait'],
timeout=self.params['timeout'])
except self.sdk.exceptions.ResourceTimeout:
diff['after'] = volume
self.exit_json(changed=changed, diff=diff)
self.exit_json(changed=changed, diff=diff)
def run(self):
state = self.params['state']
if self.conn.volume_exists(self.params['display_name']):
volume = self.conn.get_volume(self.params['display_name'])
else:
volume = None
sdk, cloud = openstack_cloud_from_module(module)
try:
if state == 'present':
_present_volume(module, cloud)
if not volume:
self._present_volume()
elif self._needs_update(volume):
self._modify_volume(volume)
else:
self.exit_json(changed=False, id=volume['id'], volume=volume)
if state == 'absent':
_absent_volume(module, cloud, sdk)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
self._absent_volume(volume)
def main():
module = VolumeModule()
module()
if __name__ == '__main__':

View File

@@ -0,0 +1,221 @@
#!/usr/bin/python
# coding: utf-8 -*-
#
# Copyright (c) 2020 by Open Telekom Cloud, operated by T-Systems International GmbH
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: volume_backup
short_description: Add/Delete Volume backup
extends_documentation_fragment: openstack.cloud.openstack
author: OpenStack Ansible SIG
description:
- Add or Remove Volume Backup in OTC.
options:
display_name:
description:
- Name that has to be given to the backup
required: true
type: str
aliases: ['name']
display_description:
description:
- String describing the backup
required: false
type: str
aliases: ['description']
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
volume:
description:
- Name or ID of the volume. Required when state is True.
type: str
required: False
snapshot:
description: Name or ID of the Snapshot to take backup of
type: str
force:
description:
- Indicates whether to backup, even if the volume is attached.
type: bool
default: False
metadata:
description: Metadata for the backup
type: dict
incremental:
description: The backup mode
type: bool
default: False
requirements: ["openstacksdk"]
'''
RETURN = '''
id:
description: The Volume backup ID.
returned: On success when C(state=present)
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
backup:
description: Dictionary describing the Cluster.
returned: On success when C(state=present)
type: complex
contains:
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
name:
description: Name given to the load balancer.
type: str
sample: "elb_test"
'''
EXAMPLES = '''
- name: Create backup
openstack.cloud.volume_backup:
display_name: test_volume_backup
volume: "test_volume"
- name: Create backup from snapshot
openstack.cloud.volume_backup:
display_name: test_volume_backup
volume: "test_volume"
snapshot: "test_snapshot"
- name: Delete volume backup
openstack.cloud.volume_backup:
display_name: test_volume_backup
state: absent
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class VolumeBackupModule(OpenStackModule):
module_min_sdk_version = '0.49.0'
argument_spec = dict(
display_name=dict(required=True, aliases=['name'], type='str'),
display_description=dict(required=False, aliases=['description'],
type='str'),
volume=dict(required=False, type='str'),
snapshot=dict(required=False, type='str'),
state=dict(default='present', type='str', choices=['absent', 'present']),
force=dict(default=False, type='bool'),
metadata=dict(required=False, type='dict'),
incremental=dict(required=False, default=False, type='bool')
)
module_kwargs = dict(
required_if=[
('state', 'present', ['volume'])
],
supports_check_mode=True
)
def _create_backup(self):
if self.ansible.check_mode:
self.exit_json(changed=True)
name = self.params['display_name']
description = self.params['display_description']
volume = self.params['volume']
snapshot = self.params['snapshot']
force = self.params['force']
is_incremental = self.params['incremental']
metadata = self.params['metadata']
changed = False
cloud_volume = self.conn.block_storage.find_volume(volume)
cloud_snapshot_id = None
attrs = {
'name': name,
'volume_id': cloud_volume.id,
'force': force,
'is_incremental': is_incremental
}
if snapshot:
cloud_snapshot_id = self.conn.block_storage.find_snapshot(
snapshot, ignore_missing=False).id
attrs['snapshot_id'] = cloud_snapshot_id
if metadata:
attrs['metadata'] = metadata
if description:
attrs['description'] = description
backup = self.conn.block_storage.create_backup(**attrs)
changed = True
if self.params['wait']:
try:
backup = self.conn.block_storage.wait_for_status(
backup,
status='available',
wait=self.params['timeout'])
self.exit_json(
changed=True, volume_backup=backup.to_dict(), id=backup.id
)
except self.sdk.exceptions.ResourceTimeout:
self.fail_json(
msg='Timeout failure waiting for backup '
'to complete'
)
self.exit_json(
changed=changed, volume_backup=backup.to_dict(), id=backup.id
)
def _delete_backup(self, backup):
if self.ansible.check_mode:
self.exit_json(changed=True)
if backup:
self.conn.block_storage.delete_backup(backup)
if self.params['wait']:
try:
self.conn.block_storage.wait_for_delete(
backup,
interval=2,
wait=self.params['timeout'])
except self.sdk.exceptions.ResourceTimeout:
self.fail_json(
msg='Timeout failure waiting for backup '
'to be deleted'
)
self.exit_json(changed=True)
def run(self):
name = self.params['display_name']
backup = self.conn.block_storage.find_backup(name)
if self.params['state'] == 'present':
if not backup:
self._create_backup()
else:
# For the moment we do not support backup update, since SDK
# doesn't support it either => do nothing
self.exit_json(changed=False)
elif self.params['state'] == 'absent':
self._delete_backup(backup)
def main():
module = VolumeBackupModule()
module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,123 @@
#!/usr/bin/python
# coding: utf-8 -*-
#
# Copyright (c) 2020 by Open Telekom Cloud, operated by T-Systems International GmbH
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: volume_backup_info
short_description: Get Backups
author: OpenStack Ansible SIG
description:
- Get Backup info from the Openstack cloud.
options:
name:
description:
- Name of the Backup.
type: str
volume:
description:
- Name of the volume.
type: str
requirements: ["openstacksdk"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
volume_backups:
description: List of dictionaries describing volume backups.
type: list
elements: dict
returned: always.
contains:
availability_zone:
description: Backup availability zone.
type: str
created_at:
description: Backup creation time.
type: str
description:
description: Backup desciption.
type: str
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
is_incremental:
description: Backup incremental property.
type: bool
metadata:
description: Backup metadata.
type: dict
name:
description: Backup Name.
type: str
snapshot_id:
description: Snapshot ID.
type: str
status:
description: Backup status.
type: str
updated_at:
description: Backup update time.
type: str
volume_id:
description: Volume ID.
type: str
'''
EXAMPLES = '''
# Get backups.
- openstack.cloud.volume_backup_info:
register: backup
- openstack.cloud.volume_backup_info:
name: my_fake_backup
register: backup
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class VolumeBackupInfoModule(OpenStackModule):
module_min_sdk_version = '0.49.0'
argument_spec = dict(
name=dict(required=False, type='str'),
volume=dict(required=False, type='str')
)
def run(self):
name_filter = self.params['name']
volume = self.params['volume']
data = []
attrs = {}
if name_filter:
attrs['name'] = name_filter
if volume:
attrs['volume_id'] = self.conn.block_storage.find_volume(volume)
for raw in self.conn.block_storage.backups(**attrs):
dt = raw.to_dict()
dt.pop('location')
data.append(dt)
self.exit_json(
changed=False,
volume_backups=data
)
def main():
module = VolumeBackupInfoModule()
module()
if __name__ == '__main__':
main()

View File

@@ -16,12 +16,10 @@ options:
description:
- Whether to provide additional information about volumes
type: bool
default: false
all_projects:
description:
- Whether return the volumes in all projects
type: bool
default: false
name:
description:
- Name of the volume as a string.
@@ -121,19 +119,20 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class VolumeInfoModule(OpenStackModule):
argument_spec = dict(
details=dict(type='bool', default=False),
all_projects=dict(type='bool', default=False),
details=dict(type='bool', required=False),
all_projects=dict(type='bool', required=False, min_ver='0.19'),
name=dict(type='str', required=False),
status=dict(type='str', required=False),
)
def run(self):
result = self.conn.block_storage.volumes(
kwargs = self.check_versioned(
details=self.params['details'],
name=self.params['name'],
all_projects=self.params['all_projects'],
status=self.params['status'],
)
result = self.conn.block_storage.volumes(**kwargs)
result = list(result)
self.results.update({'volumes': result})

View File

@@ -0,0 +1,133 @@
#!/usr/bin/python
# coding: utf-8 -*-
#
# Copyright (c) 2020 by Open Telekom Cloud, operated by T-Systems International GmbH
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: volume_snapshot_info
short_description: Get volume snapshots
author: OpenStack Ansible SIG
description:
- Get Volume Snapshot info from the Openstack cloud.
options:
details:
description: More detailed output
type: bool
default: True
name:
description:
- Name of the Snapshot.
type: str
volume:
description:
- Name of the volume.
type: str
status:
description:
- Specifies the snapshot status.
choices: [creating, available, error, deleting,
error_deleting, rollbacking, backing-up]
type: str
requirements: ["openstacksdk"]
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = '''
volume_snapshots:
description: List of dictionaries describing volume snapshots.
type: list
elements: dict
returned: always.
contains:
created_at:
description: Snapshot creation time.
type: str
description:
description: Snapshot desciption.
type: str
id:
description: Unique UUID.
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
metadata:
description: Snapshot metadata.
type: dict
name:
description: Snapshot Name.
type: str
status:
description: Snapshot status.
type: str
updated_at:
description: Snapshot update time.
type: str
volume_id:
description: Volume ID.
type: str
'''
EXAMPLES = '''
# Get snapshots.
- openstack.cloud.volume_snapshot_info:
register: snapshots
- openstack.cloud.volume_snapshotbackup_info:
name: my_fake_snapshot
register: snapshot
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class VolumeSnapshotInfoModule(OpenStackModule):
module_min_sdk_version = '0.49.0'
argument_spec = dict(
details=dict(default=True, type='bool'),
name=dict(required=False, type='str'),
volume=dict(required=False, type='str'),
status=dict(required=False, type='str',
choices=['creating', 'available', 'error',
'deleting', 'error_deleting', 'rollbacking',
'backing-up']),
)
def run(self):
details_filter = self.params['details']
name_filter = self.params['name']
volume_filter = self.params['volume']
status_filter = self.params['status']
data = []
query = {}
if name_filter:
query['name'] = name_filter
if volume_filter:
query['volume_id'] = self.conn.block_storage.find_volume(volume_filter)
if status_filter:
query['status'] = status_filter.lower()
for raw in self.conn.block_storage.snapshots(details_filter, **query):
dt = raw.to_dict()
dt.pop('location')
data.append(dt)
self.exit_json(
changed=False,
volume_snapshots=data
)
def main():
module = VolumeSnapshotInfoModule()
module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,11 @@
openstacksdk
ansible-base
pycodestyle
flake8
pylint
voluptuous
yamllint
rstcheck
ruamel.yaml
#galaxy-importer # see https://review.opendev.org/#/c/743054
tox

View File

@@ -0,0 +1,11 @@
openstacksdk
ansible-core
pycodestyle
flake8
pylint
voluptuous
yamllint
rstcheck
ruamel.yaml
#galaxy-importer # see https://review.opendev.org/#/c/743054
tox

View File

@@ -1,5 +1,5 @@
openstacksdk
ansible
ansible<2.10
pycodestyle
flake8
pylint

View File

@@ -1,11 +0,0 @@
openstacksdk
ansible-base
pycodestyle
flake8
pylint
voluptuous
yamllint
rstcheck
ruamel.yaml
#galaxy-importer # see https://review.opendev.org/#/c/743054
tox

1
test-requirements.txt Symbolic link
View File

@@ -0,0 +1 @@
test-requirements-2.10.txt

View File

@@ -2,8 +2,6 @@ plugins/modules/compute_flavor_info.py pylint:ansible-deprecated-no-collection-n
plugins/modules/identity_domain_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/identity_user_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/image_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/networks_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/port_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/project_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/subnets_info.py pylint:ansible-deprecated-no-collection-name
plugins/module_utils/openstack.py pylint:ansible-deprecated-no-collection-name

View File

@@ -2,8 +2,6 @@ plugins/modules/compute_flavor_info.py pylint:ansible-deprecated-no-collection-n
plugins/modules/identity_domain_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/identity_user_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/image_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/networks_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/port_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/project_info.py pylint:ansible-deprecated-no-collection-name
plugins/modules/subnets_info.py pylint:ansible-deprecated-no-collection-name
plugins/module_utils/openstack.py pylint:ansible-deprecated-no-collection-name

15
tox.ini
View File

@@ -31,9 +31,6 @@ commands = stestr run {posargs}
commands =
flake8
# NOTE(mordred): Until ansible 2.10 is released we need to override deps for this env
# here because we need to use 2.10 galaxy to build the collection and properly
# respect build_ignore
[testenv:build]
deps =
pbr
@@ -62,6 +59,12 @@ commands = {[testenv:linters]commands}
deps =
-r{toxinidir}/test-requirements-2.9.txt
[testenv:linters-2.11]
passenv = {[testenv:linters]passenv}
commands = {[testenv:linters]commands}
deps =
-r{toxinidir}/test-requirements-2.11.txt
[testenv:venv]
deps =
-r{toxinidir}/test-requirements.txt
@@ -102,3 +105,9 @@ deps =
-r{toxinidir}/test-requirements-2.9.txt
passenv = {[testenv:ansible]passenv}
commands = {[testenv:ansible]commands}
[testenv:ansible-2.11]
deps =
-r{toxinidir}/test-requirements-2.11.txt
passenv = {[testenv:ansible]passenv}
commands = {[testenv:ansible]commands}