142 Commits

Author SHA1 Message Date
Zuul
ccec4d07b3 Merge "Add support for managing network segments" 2026-03-23 15:16:45 +00:00
Zuul
04b70b99da Merge "Support updating extra_specs in project module" 2026-03-19 21:41:00 +00:00
Zuul
ed4c4036af Merge "feat(images): Adds support for image ID reservation and queued images" 2026-03-19 07:37:34 +00:00
Zuul
e2ebc1c8d0 Merge "tox: Drop basepython" 2026-03-19 07:37:33 +00:00
Zuul
99eb60f7dc Merge "Add baremetal_port_group module" 2026-03-19 06:56:08 +00:00
Nicholas Kuechler
e90fd7a915 feat(images): Adds support for image ID reservation and queued images
Change-Id: I3aa319deb711eaa1ccad4f48eedb079afd801872
Signed-off-by: Nicholas Kuechler <nicholas.kuechler@rackspace.com>
2026-03-12 14:52:48 -05:00
Artem Goncharov
1bc4f648fb Rotate the galaxy secret
Change-Id: I4d25f3b7831c80c615e6dd967a5e5065452cdce8
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
2026-02-17 17:15:54 +01:00
Grzegorz Koper
1a654a9c38 Add baremetal_port_group module
Add support for managing Ironic baremetal port groups.

Include CI role coverage and unit tests for create, update, delete, and check mode behavior.

Add a reno fragment describing the new module.

Tests-Run: python -m pytest tests/unit/modules/cloud/openstack/test_baremetal_port_group.py
Change-Id: I98564fcb5b81a1dd7be1fbf5ffca364483296655
2026-02-10 14:45:35 +01:00
Ivan Anfimov
67b7ec5e58 tox: Drop basepython
Python 2 reached its EOL long time ago and we no longer expect any
user may attempt to run tox in Python 2.

Removing the option allows us to remove ignore_basepython_conflict.

Change-Id: I54c0581e77c924f9d98975964e4470e89fa3d954
Co-authored-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
Signed-off-by: Ivan Anfimov <lazekteam@gmail.com>
2026-01-24 12:53:27 +00:00
naosuke
70128d6230 Support updating extra_specs in project module
It supports for adding extra_specs in updating project.

Change-Id: I98a73ed9367d52df82213b3b7c484ceac10acf3d
Signed-off-by: Naoki Hanakawa <naoki.hanakawa@lycorp.co.jp>
2025-12-17 10:13:52 +09:00
Riccardo Pittau
1dc367b566 Use new bifrost CI jobs names
Ironic do not support tinyipa anymore, all jobs run with DIB
based ipa ramdisks, so we updated their config and names.

Change-Id: Id7b260a0965d941d3a34eb181a068a9a5e7189ef
Signed-off-by: Riccardo Pittau <elfosardo@gmail.com>
2025-12-16 09:28:32 +01:00
Maksim Malchuk
a178493281 Drop duplicate lines in the Changelog
Change-Id: I0475545d8114d8731a2131eed372c7557b579e3f
Signed-off-by: Maksim Malchuk <maksim.malchuk@gmail.com>
2025-11-17 16:19:28 +03:00
Austin Jamias
b2aac80b41 Fix tox.ini linters_2_18 config
Correct a typo where linters_2_18's requirements were not being
referenced correctly.

Change-Id: I1c7a7555c9effd4f0ceb417be709059aa10d0a5e
Signed-off-by: Austin Jamias <ajamias@redhat.com>
2025-11-10 19:45:36 -05:00
Zuul
ae6e48be00 Merge "Fix Ansible errors" 2025-10-31 18:03:37 +00:00
Michal Nasiadka
e06a61f97a Fix Ansible errors
Change-Id: I826ec0b01f8cfdf78235d146c90d790c8e891cc9
Signed-off-by: Michal Nasiadka <mnasiadka@gmail.com>
2025-10-27 07:40:26 +00:00
Artem Goncharov
3c0a9f2d94 Release 2.5.0 version
Change-Id: Ie96930eda27984833f386bbcd65ebf2eeda4e40c
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
2025-10-21 13:09:36 +02:00
Tadas Sutkaitis
b1ecd54a5d feat: introduce share_type modules
Add share_type and share_type_info modules.
Uses direct Manila API calls via the SDK's session/connection interface
since share type resources are not available in openstacksdk.

Change-Id: I49af9a53435e226c5cc93a14190f85ef4637c798
Signed-off-by: Tadas Sutkaitis <tadasas@gmail.com>
2025-10-08 20:51:26 +00:00
Zuul
4a3460e727 Merge "Add import_method to module" 2025-10-07 17:13:52 +00:00
Jay Jahns
c9887b3a23 Add import_method to module
This adds the import method to support web-download option. It
has been added to the openstacksdk.

Closes-Bug: #2115023
Change-Id: I3236cb50b1265e0d7596ada9122aa3b4fc2baf9e
Depends-On: https://review.opendev.org/c/openstack/ansible-collections-openstack/+/955752
2025-08-26 12:04:42 +00:00
Andrew Bonney
e84ebb0773 ci: fix various issues preventing complete runs
When creating a coe cluster, a flavor lookup occurs which
fails as nothing is specified. This adds a default flavor
which can be used to test cluster provisioning.

Various checks used a deprecated truthy check which no
longer works with Ansible 2.19

Inventory cache format change in Ansible 2.19

galaxy.yml required in tmp testing directory or version
checks fail for linters with Ansible 2.19

Change-Id: Iaf3f05d0841a541e4318821fe44ddd59f236b640
2025-07-25 10:15:30 +01:00
Andrew Bonney
eef8368e6f Add support for managing network segments
Adds a module to manage Neutron network segments where the
segmentation plugin is enabled.

Segments are relatively simple and do not support modification
beyond the name/description, so most attributes are used for
initial segment creation, or filtering results in order to
perform updates.

Depends-On: https://review.opendev.org/c/openstack/ansible-collections-openstack/+/955752
Change-Id: I4647fd96aaa15460d82765365f98a18ddf2693db
2025-07-24 09:04:30 +00:00
Zuul
f584c54dfd Merge "Add volume_manage module" 2025-06-05 12:42:20 +00:00
Zuul
3901000119 Merge "feat: add support for filters in inventory" 2025-06-05 12:08:15 +00:00
Simon Dodsley
556208fc3c Add volume_manage module
This module introduces the ability to use the cinder manage
and unmanage of an existing volume on a cinder backend.

Due to API limitations, when unmanaging a volume, only the
volume ID can be provided.

Change-Id: If969f198864e6bd65dbb9fce4923af1674da34bc
2025-05-31 09:46:52 -04:00
Zuul
3ac95541da Merge "Shows missing data in stack_info module output" 2025-05-13 19:23:35 +00:00
Zuul
59b5b33557 Merge "Let clouds_yaml_path behave as documented (Override path to clouds.yaml file)" 2025-05-13 19:23:34 +00:00
Zuul
3d7948a4e4 Merge "Don't compare current state for reboot_* actions" 2025-05-13 18:15:17 +00:00
Paulo Dias
6cb5ed4b84 feat: add support for filters in inventory
Change-Id: Id8428ce1b590b7b2c409623f180f8f8e608e1cda
Signed-off-by: Paulo Dias <paulodias.gm@gmail.com>
2025-05-13 17:21:47 +00:00
matthias-rabe
283c342b10 Let clouds_yaml_path behave as documented (Override path to clouds.yaml file)
Change-Id: I01b38467c4bea5884cbbd04e1e3a2e7c6eeebfeb
2025-04-25 09:55:36 +02:00
Roberto Alfieri
2a5fb584e2 Shows missing data in stack_info module output
The generator `stacks` object didn't return all the values (e.g. output
and parameters) so `get_stack` is used to populate the dict.

Closes-Bug: #2059771
Change-Id: Ie9061e35fc4bf217d76eee96f07e0ed68e44927c
Signed-off-by: Roberto Alfieri <ralfieri@redhat.com>
2025-04-24 22:45:15 +00:00
Zuul
762fee2bad Merge "Allow role_assignment module to work cross domain" 2025-04-24 15:30:33 +00:00
Zuul
9d92897522 Merge "Fix example in the dns_zone_info module doc" 2025-04-24 13:35:53 +00:00
Zuul
df0b57a3c6 Merge "Fix router module external IPs when only subnet specified" 2025-04-24 10:03:02 +00:00
Gavin Chappell
5a6f5084dd Don't compare current state for reboot_* actions
When performing these actions a server will start `ACTIVE` and end
`ACTIVE` meaning that the Ansible module skips over the host you are
trying to reboot because it looks like the action was already taken.

Tests are updated to reflect `changed=True` with the end state
remaining as `ACTIVE`

Closes-Bug: 2046429

Change-Id: I8828f05bb5402fd2ba2c26b67c727abfbcc43202
2025-04-23 18:18:48 +01:00
Roberto Alfieri
c988b3bcbf Fix example in the dns_zone_info module doc
The example in `dns_zone_info` documentation showed an incorrect
`openstack.cloud.dns_zones` module instead of
`openstack.cloud.dns_zone_info`.

Closes-Bug: #2065471
Change-Id: Ic9484034f6631306c031ed640979bec009672ade
Signed-off-by: Roberto Alfieri <me@rebtoor.com>
2025-04-23 16:14:59 +00:00
Doug Goldstein
437438e33c Allow role_assignment module to work cross domain
The role_assignment module always looks up the user, group and project
so to support cross-domain assignments we should add extra parameters
like OSC to look them up from the correct domains. Switch to using
the service proxy interface to grant or revoke the roles as well.

Partial-Bug: #2052448
Partial-Bug: #2047151
Partial-Bug: #2097203
Change-Id: Id023cb9e7017c749bc39bba2091921154a413723
2025-04-23 09:21:43 -05:00
Mohammed Naser
3fd79d342c Fix disable_gateway_ip for subnet
When creating a subnet with disable_gateway_ip, the value
was not passed into the creation which would lead into
a subnet being created with a gateway, and then on updates
it would have the gateway removed.

Change-Id: I816d4a4d09b2116c00cf868a590bd92dac4bfc5b
2025-04-22 18:25:52 +00:00
Andrew Bonney
98bb212ae4 Fix router module external IPs when only subnet specified
The router module used to support adding external fixed IPs
by specifying the subnet only, and the documentation still
allows for this. Unfortunately the logic currently results
in an error message stating that the ip_address cannot be
null.

This patch reinstates this functionality and adds tests to
avoid a future regression.

Change-Id: Ie29c7b2b763a58ea107cc50507e99f650ee9e53f
2025-04-22 16:28:02 +00:00
Doug Goldstein
08d8cd8c25 fix test failures on recordset
Since long time we knew that it is necessary to explicitly invoke
`to_dict` for openstacksdk resources before passing them to Ansible.
Here it was missed and in addition to that records are only returned
when the recordset becomes active.

Change-Id: I49238d2f7add9412bb9100b69f1b84b512f8c34b
2025-04-22 16:30:58 +03:00
Doug Goldstein
41cf92df99 replace storyboard links with bugs.launchpad.net
When you go to the storyboard link at the top it tells users that issues
and bugs are now tracked at bugs.launchpad.net so just update all the
links to point there.

Change-Id: I1dadae24ef4ca6ee2d244cc2a114cca5e4ea5a6b
2025-04-02 09:44:59 -05:00
Callum Dickinson
5494d153b1 Add the object_containers_info module
This adds a module for getting information on one or more
object storage containers from OpenStack.

The following options are supported:

* name - Get details for a single container by name.
  When this parameter is defined a single container is returned,
  with extra metadata available (with the same keys and value
  as in the existing object_container module).
* prefix - Search for and return a list of containers by prefix.
  When searching for containers, only a subset of metadata values
  are available.

When no options are specified, all containers in the project
are returned, with the same metadata available as when the
prefix option is used.

Change-Id: I8ba434a86050f72d8ce85c9e98731f6ef552fc79
2025-01-24 10:43:18 +13:00
Zuul
fef560eb5b Merge "CI: add retries for trait test" 2025-01-20 19:41:02 +00:00
Sagi Shnaidman
1ce7dd8b5f Release 2.4.1 version
Change-Id: I5d5532865c8700030925a1dcd6e262c53f37c0a9
2025-01-20 17:58:47 +02:00
Zuul
438bbea34b Merge "Fix missed client_cert in OpenStackModule" 2025-01-18 20:37:18 +00:00
Sagi Shnaidman
239c45c78f CI: add retries for trait test
Change-Id: Ic94f521950da75128a6a677111d4f0206a0e33d6
2025-01-18 13:03:43 +02:00
Anna Arhipova
e065818024 Update tags when changing server
Tags are not included in the metadata and are not supported by PUT
/servers/{server_id}, requiring a specific mechanism for modification.

With this fix, existing tags on the server will be completely replaced
by the new set of tags provided during the update

Also optimized tags tests a bit

Change-Id: I2673ed57d848654427dcb7ed7aceba811a3dd314
2025-01-17 20:49:50 +01:00
Victor Chembaev
0764e671a9 Fix missed client_cert in OpenStackModule
Change-Id: Ia6bc31c18f0707047c62e8824f7e9e489284bdab
2025-01-17 14:41:32 +02:00
Sagi Shnaidman
5f4db3583e Release 2.4.0 version
Change-Id: I7e09a849d089962aaec90d30d9e6ab8a3891aa41
2025-01-15 18:18:47 +02:00
Anna Arhipova
6262474c94 Allow create instance with tags
Change-Id: I98a04c18ac0a815841c6c474b39af5e9ed4d1c0d
2025-01-14 17:24:45 +00:00
Freerk-Ole Zakfeld
f9fcd35018 Add Traits Module
Change-Id: I7db759f716c1154cb0dd30e240af3a0420efab8f
Signed-off-by: Freerk-Ole Zakfeld <fzakfeld@scaleuptech.com>
2025-01-14 10:34:50 +01:00
Zuul
5dbf47cb49 Merge "Add loadbalancer quota options" 2025-01-09 18:43:07 +00:00
Tom Clark
57c63e7918 Add loadbalancer quota options
Enable configuration of loadbalancer options within the quota module so that the current collection can be used to address Octavia quotas.

Change-Id: I1dd593f5387929ed522d54954fc63dd535df7c7c
2025-01-09 13:45:19 +00:00
Sagi Shnaidman
ed5829d462 Release 2.3.3 version
Change-Id: I9d78fdc98fdb986c926f196077d9b401de573218
2024-12-22 16:57:48 +02:00
Zuul
b025e7c356 Merge "Fix deprecated ANSIBLE_COLLECTIONS_PATHS variable" 2024-12-22 14:38:13 +00:00
Sagi Shnaidman
782340833e Fix deprecated ANSIBLE_COLLECTIONS_PATHS variable
Change it to ANSIBLE_COLLECTIONS_PATH

Change-Id: I6100e1da0e578c26dd90c05b7bd5eeddcaec983b
2024-12-22 14:54:52 +02:00
Sagi Shnaidman
73aab9e80c Add test to only_ipv4 in inventory
Change-Id: I70cbfef05af4853947fac0040c4a3a9cf6c2f1fe
2024-12-22 13:55:33 +02:00
Zuul
ae7e8260a3 Merge "add an option to use only IPv4 only for ansible_host and ansible_ssh_host" 2024-12-22 11:51:13 +00:00
Sagi Shnaidman
030df96dc0 Release 2.3.2 version
Change-Id: I0234ad386309685066ede85286a5d3938c9d6133
2024-12-20 11:46:16 +02:00
Kevin Honka
c5d0d3ec82 add an option to use only IPv4 only for ansible_host and ansible_ssh_host
By default the openstack inventory fetches the first fixed ip
address for "ansible_host" and "ansible_ssh_host".
Due to the random sorting of addresses by OpenStack, this can either be
a ipv4 or ipv6 address, this is not desirable in some legacy setups
where Ansible runs in a ipv4 only setup,
but OpenStack is already ipv6 enabled.

To prevent this, a new option called "only_ipv4" was added,
which forces the inventory plugin to use only fixed ipv4 addresses.

Closes-Bug: #2051249
Change-Id: I3aa9868c9299705d4b0dcbf9b9cb561c0855c11c
2024-12-20 06:36:04 +00:00
Zuul
0cff7eb3a2 Merge "Fix openstack.cloud.port module failure in check mode" 2024-12-19 23:07:20 +00:00
Hirano Yuki
29e3f3dac8 Fix openstack.cloud.port module failure in check mode
The openstack.cloud.port module always fails in check mode due to it
calls PortModule._will_change() with the wrong number of arguments.

Change-Id: I7e8a4473df8bb27d888366b444a54d3f7b1c2fa8
2024-12-19 19:01:22 +00:00
Takashi Kajinami
d18ea87091 Drop compat implementations for tests
Python < 3.4.4 has already reached its EOL.

Change-Id: Ieb7cfd796991645db44dafc0e5ba82855c08853a
2024-12-19 19:00:27 +00:00
Sagi Shnaidman
529c1e8dcc Fix release job
Set up Ansible in virtualenv

Change-Id: I0ee92fe621423f9f199b2baab6a95fba377b65a1
2024-12-19 12:52:07 +02:00
Sagi Shnaidman
99b7af529c Release 2.3.1 version
Change-Id: I8fa2c36d08e387c93f61630b55892ff69b4eff6a
2024-12-18 13:27:39 +02:00
Sagi Shnaidman
2ed3ffe1d0 Change token for new Ansible Galaxy server
Change it with:

zuul-client encrypt  \
  --tenant openstack \
  --project openstack/ansible-collections-openstack
(zuul URl is https://zuul.opendev.org)

Change-Id: I9623f14453fd1fd9631195cc3794141edfce0cf7
2024-12-16 21:37:33 +02:00
Victor Chembaev
ae5dbf0fc0 Add ability to pass client tls certificate
Add ablity to pass client tls certificate
to make mTLS connection to OpenStack provider.
Closes-Bug: #2090953

Change-Id: I33ef38c830309cf4f9fae11c8403fb4e616cf315
2024-12-14 21:42:50 +00:00
Sagi Shnaidman
4074db1bd0 Replace 2.16 jobs by 2.18 Ansible jobs
Change-Id: I4cae065e961a06d44792bef2e6479c30fe242911
2024-12-13 21:41:53 +00:00
Sagi Shnaidman
3248ba9960 CI: wait until server is shelved offloaded
Wait for condition when shelving is finished.

Change-Id: I7945989461c0b80adbb629da2eef1464e1ce2645
2024-12-13 23:39:48 +02:00
Sagi Shnaidman
2d5ca42629 Fix CI and add 2.16 Ansible job
Change-Id: Idcb5d2db4a92084239b80703be6a80de3e9f1116
2024-12-10 17:00:47 +02:00
Sagi Shnaidman
83456005fc Release 2.3.0 version
Change-Id: Ibe054979b707645722f9dd1133b40db95f1aba4e
2024-11-28 19:19:51 +02:00
Zuul
5b61019a34 Merge "Add module to filter available volume services." 2024-11-07 20:04:50 +00:00
Zuul
630dac3787 Merge "Add inactive state for the images" 2024-11-05 12:02:07 +00:00
Zuul
54c2257376 Merge "Allow to specify multiple allocation pools when creating a subnet" 2024-11-05 12:02:06 +00:00
Zuul
c843feb338 Merge "Add target_all_project option" 2024-11-05 11:56:22 +00:00
Simon Hensel
28541ee158 Allow to specify multiple allocation pools when creating a subnet
With this change, multiple allocation pool may be specified when creating
a subnet. Allocation pools are defined as a list of dictionaries.

For example:

openstack.cloud.subnet:
  name: sub1
  network: network1
  cidr: 192.168.0.0/24
  ip_version: 4
  allocation_pools:
    - start: 192.168.0.10
      end: 192.168.0.50
    - start: 192.168.0.100
      end: 192.168.0.150

Change-Id: I77a06990de082466dc6265a14c379b8bbaf789e8
2024-11-05 10:13:45 +01:00
Amir Nikpour
c09029ada0 Add target_all_project option
Adds target_all_project option to neutron_rbac_policy
module, for specifing all projects as target projects
explicitly.

Change-Id: I1393463a79fc83bcda7aa5642f5d3ed27fb195b5
2024-11-05 10:32:08 +03:30
Gaël THEROND (Fl1nt)
903eeb7db7 Add module to filter available volume services.
Add a way to filter which volume service is running on a host or list
which hosts run available volume services.

Closes-Bug: #2010490
Change-Id: Icb17f6019a61d9346472d83ddcd2ad29c340ea05
2024-11-04 13:54:15 +00:00
Christian Berendt
03ebbaed93 Fix typo in openstack.cloud.lb_pool
Change-Id: Ic265ce384d9d4392984bc400e6a286831396d472
2024-11-03 12:15:11 +00:00
Dmitriy Rabotyagov
8d9b8b9a35 Add inactive state for the images
Glance images can be deactivated ad reactivated with corresponding
API calls. It might be useful for operators to be able to control
these states through ansible modules as well. Instead of introduction
of the new parameter we're adding new state for the image that is
`inactive`.

Change-Id: I0738ff564f81a31690872450a4731340ed6bbeb1
2024-11-03 12:12:34 +00:00
Zuul
fef5e127d4 Merge "Allow wait: false when auto_ip is false" 2024-10-31 15:53:55 +00:00
Zuul
01d3011895 Merge "Enable glance-direct interop image import" 2024-10-31 13:51:29 +00:00
James Denton
d4f25d2282 Allow wait: false when auto_ip is false
There is an issue with the logic that results in a failure
to create a server when auto_ip is false. This patch tests
for the bool value of auto_ip and the two lists rather that
None.

Closes-Bug: #2049046
Change-Id: I2664c087c4bde83c4033ab3eb9d3e97dafb9e5cb
Signed-off-by: James Denton <james.denton@rackspace.com>
Signed-off-by: Kevin Carter <kevin.carter@rackspace.com>
2024-10-30 17:44:06 -05:00
Sagi Shnaidman
3df5c38ca6 Fix test for server shelve
Change-Id: I6adb15e82817a4e08ed102a722d9fb44ebe80333
2024-10-29 17:03:36 +02:00
Jay Jahns
474b3804eb Enable glance-direct interop image import
Adds the use_import parameter to enable interop import so
images that need transformation by glance can have it, such as
format conversion.

Closes-Bug: 2084481
Change-Id: I39d1e94ff8ab9f0e0b99c1cef9a814eef0b1f060
2024-10-15 00:29:42 -05:00
Zuul
5a8ad4cdf0 Merge "Fix typo in parameter description" 2024-10-10 21:20:14 +00:00
Zuul
e742016861 Merge "Ensure coe_cluster_template compare labels properly" 2024-10-10 17:03:16 +00:00
Dmitriy Rabotyagov
40a384214c Ensure coe_cluster_template compare labels properly
SDK does return all keys and values of the template labels as
strings. At the same time user can define some labels as integers or
booleans, which will break comparison of labels and lead to module
failure on consecutive runs.

Change-Id: I7ab624428c8bb06030a2b28888f5cb89bb249f08
2024-10-09 20:56:10 +00:00
Pierre Riteau
1188af2486 Fix typo in parameter description
Change-Id: Ia9d4c5b2c8bb27c6088de71c6e55e160d0427ff7
2024-10-09 20:54:02 +00:00
Tobias Urdin
e42e21f621 Add Neutron trunk module
This adds a Ansible module for managing a
Neutron trunk and the sub ports associated
with the trunk.

Change-Id: I0e1c6798b6cc30062c881d1f92fdd4d630d31106
2024-10-09 14:35:52 +00:00
Zuul
e6379e3038 Merge "Fix exception when creating object from file" 2024-10-09 09:08:35 +00:00
Vahid Mohsseni
e0dc4776bb Fix exception when updating container with metadata
A ValueError is raised when running the object_container module with the
`metadata` param against a container with existing metadata.

When the module attempts to enumerate the existing container metadata, a
ValueError exception is raised, because the code is iterating over the
metadata keys, instead of `dict_items`.
Compare to the iteration through another dict `metadata` on the next
line:

    new_metadata = dict((k, v) for k, v in metadata.items()

This change adds a call to `items()` on the dictionary.
Note that this is added outside the parentheses so that the behaviour of the
`or` statement is not affected, and that another exception isn't caused
if `container.metadata` is not a dict.

Closes-Bug: #2071934
Change-Id: Ie5e1f275839e38340a75ab18c3b9ec9bc7745d68
2024-10-09 08:57:18 +11:00
Ben Formosa
22941e86d1 Fix exception when creating object from file
When creating a new object from file, a AttributeError is raised.

This is caused because the sdk does not return anything when creating an
object from a file.

With this change, the `_create` function will always return an object.

Closes-Bug: #2061604
Change-Id: I34cefd1bb10c6eef784e37d26122e5ed2c72488d
2024-10-09 08:53:28 +11:00
Zuul
b089d56136 Merge "Fix regression in quota module" 2024-10-08 15:43:40 +00:00
Zuul
536fe731f7 Merge "Add application_credential module" 2024-10-08 14:27:36 +00:00
Ben Formosa
4a8214b196 Fix regression in quota module
I suspect that the change to `update_quota_set` in openstacksdk commit
[9145dce64](https://opendev.org/openstack/openstacksdk/commit/9145dcec64)
has caused a regession in the quota module, making it not work correctly
for volume and compute quotas.

This change updates the calls to `update_quota_set` with the new
signatures.

Closes-Bug: #2068568
Change-Id: I604a8ffb08a76c20397f43c0ed3b23ddb11e53eb
2024-10-08 16:24:04 +03:00
Sagi Shnaidman
d1b77a3546 Remove 2.9 jobs from Zuul config
It's old and obsolete version, we don't need to run on it.
Change-Id: I1ca3bb1a4c513af1227c08ace4e9df69dca1a4d3
2024-10-08 13:47:14 +03:00
Clark Boylan
8aefe5dc3a Run functional testing regardless of pep8/linter results
The parent change was pushed up initially with a super minor pep8 under
indentation issue. The Zuul config at the time prevent functional
testing from running so we got no indiciation of whether or not the
proposed fix actually fixed the issue.

Update the Zuul config to run pep8 and functional tests concurrently.
Formatting errors are independent of functionality and getting quick
feedback on both gives contributors and reviewers as much feedback as
possible when making decisions about the next step for the code review
process.

The fewer round trips we force everyone to make the less likely we are
to forget about a change or ignore it and otherwise extend the time it
takes to get code merged.

Change-Id: Ib92b3b80f2873327161e23b0ce6bfc6c34850538
2024-10-08 08:11:10 +00:00
Sagi Shnaidman
217e965357 Fix CI in collection
Temporarly disable the quota tests

Change-Id: I7a064649276c1fe0fde025c85817682b0429086a
Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
2024-10-08 00:14:27 +03:00
Steve Baker
94afde008b Add application_credential module
Create or delete a Keystone application credential.  When the secret
parameter is not set a secret will be generated and returned in the
response. Existing credentials cannot be modified so running this module
against an existing credential will result in it being deleted and
recreated. This needs to be taken into account when the secret is
generated, as the secret will change on each run of the module.

The returned result also includes a usable cloud config which allows
playbooks to easily run openstack tasks using the credential created by
this module.

Change-Id: I0ed86dc8785b0e9d10cc89cd9137a11d02d03945
2024-06-26 01:57:55 +00:00
Zuul
032a5222c1 Merge "Add vlan_tranparency for creation networks" 2024-06-25 23:01:13 +00:00
Zuul
fa6eb547ab Merge "Add insecure_registry property to coe_cluster_templates" 2024-06-25 19:49:25 +00:00
Artem Goncharov
23290a568b Allow munch results in server_info module
Certain branches of the openstacksdk are explicitly converting
`Resource` objects to munch objects to add additional virtual
properties. This means that the module may receive `Resource` or a
`Munch` object. Add a small check.

Change-Id: I413877128d1e2b68d7f39420d19e2560d3d9a99e
2024-06-25 09:27:32 +00:00
Artem Goncharov
69801f268f Wait for deleted server to disappear from results
When we delete server wait for it to completely disappear from the
results (Nova returns it for some time with the 'DELETED' state). Since
tests (and actually also users) not able to really cope with this wait
for server to be gone completely.

Change-Id: Ie2dde98ae47dd7108d554495d5025df175647d5c
2024-06-24 17:56:27 +02:00
Fiorella Yanac
0c6aee5378 Add vlan_tranparency for creation networks
vlan_tranparency can be enabled for creation networks.

Change-Id: If1a874d28507ac89eed38426e97f1a58db020965
2024-06-19 13:43:24 +01:00
Dmitriy Rabotyagov
fb258ababa Add insecure_registry property to coe_cluster_templates
This property was missing from API documentation while being supported
for quite some time.

Change-Id: Idc1e1d78cb33f3183af00ee19446ccfd1f00f266
2024-05-02 16:59:12 +02:00
Slawek Kaplonski
4c186a2ae6 Add support for creation of the default external networks
In Neutron external network can be marked as 'default' and such network
will be used in the auto allocate network functionality [1].
This patch adds support for creation of such default network by the
ansible openstack module.

[1] https://docs.openstack.org/neutron/latest/admin/config-auto-allocation.html

Change-Id: I1aeb91f8142cdc506c3343871e95dcad13f44da0
2024-05-02 10:21:38 +02:00
Zuul
598cc2d743 Merge "fix: subnet module: allow cidr option with subnet_pool" 2024-04-08 14:40:10 +00:00
George Shuklin
e0139fe940 fix: subnet module: allow cidr option with subnet_pool
Specifying CIDR during creation of subnet from subnet pool is a valid
operation. Moreover, in case of use of a subnet pool with multiple
subnets, cidr is a mandatory paramter for creating subnet.

Following code should be valid:

    - name: Create subnet
      openstack.cloud.subnet:
        name: "subnet_name"
        network: "some_network"
        gateway_ip: "192.168.0.1"
        allocation_pool_start: "192.168.0.2"
        allocation_pool_end: "192.168.0.254"
        cidr: "192.168.0.0/24"
        ip_version: 4
        subnet_pool: "192.168.0.0/24"

This scenario is added as a subnet-pool.yaml test in the test role.

Change-Id: I1163ba34ac3079f76dd0b7477a80a2135985a650
2024-04-05 12:30:22 +03:00
Zuul
bc65d42f52 Merge "Disable auto-discovery for setuptools" 2024-03-20 15:45:59 +00:00
Zuul
ac4d105813 Merge "router: Allow specifying external network name in a different project" 2024-03-19 17:33:20 +00:00
Joel Capitao
747c4d23bc Disable auto-discovery for setuptools
Since setuptools release (61.0.0) ansible-collection-openstack's
package build command (python3 setup.py sdist bdist_wheel) is
finding multiple top-level packages in a flat-layout automatically.

This issue is mentioned in setuptools bug 3197 [1], and the suggested
workaround is to disable auto-discovery by adding 'py_modules=[]' in
setup.py.

[1] https://github.com/pypa/setuptools/issues/3197

Change-Id: I4aef1fd59375c4a3bc9e362e7949fa153e4cbcb0
2024-03-14 15:50:28 +01:00
Steve Baker
93d51498e9 CI: Don't create port with binding profile
Creating a port with a binding profile now requires a user with the
service role. This fixes CI by removing the tasks which create a port
with a binding profile. The new policy implies that only other openstack
services should be doing this. The capability can remain in the module,
but it is unlikely to be used unless with a custom or deprecated policy.

Change-Id: I89306d35670503d2fc8e76c030d88f64c20eca08
2024-03-13 14:21:14 +13:00
Mark Goddard
cfd2d1f773 router: Allow specifying external network name in a different project
If a router is created in a specific project, the router module
tried to find its external network in the same project. This would fail
with 'No Network found for <network>' if the external network is in a
different project. This behaviour has changed, most likely in [1] when
the project scoping was added to the find_network function call.

This change modifies the network query to first check the project, then
fall back to a global search if the network is not found. This ensures
that if there are multiple networks with the name we will choose one in
the project first, while allowing use of a network in a different
project.

A regression test has been added to cover this case.

[1] 3fdbd56a58

Closes-Bug: #2049658
Change-Id: Iddc0c63a2ce3c500d7be2f8802f718a22f2895ae
2024-01-24 09:24:32 +00:00
Mark Goddard
e009f80ffc CI: Fix linters-devel and devstack tests
The linters-devel job fails with:

  ansible-test sanity: error: argument --skip-test: invalid choice:
  'metaclass-boilerplate' (choose from 'action-plugin-docs', ...)

The functional test fails with:

  The conditional check 'info1.volumes | selectattr("id", "equalto", "{{
  info.volumes.0.id }}") | list | length == 1' failed. The error was:
  Conditional is marked as unsafe, and cannot be evaluated.

This is due to a change in Ansible 2.17 preventing embedded templates
from referencing unsafe data [1].

[1] https://docs.ansible.com/ansible/latest/porting_guides/porting_guide_9.html#playbook

Change-Id: I2f8411cac1403568afb13c2b96ba452c4c81f126
2024-01-19 17:42:23 +00:00
Dmitry Tantsur
08c93cf9b1 Migrate Bifrost jobs to Ubuntu Jammy
Change-Id: I8971e760457a499129d06bb472598f25ee168a7f
2023-12-19 12:53:18 +01:00
gtema
fff978d273 Prepare release 2.2.0
Prepare data for the v2.2.0 release with few new modules and bugfixes.

Change-Id: Id593b623b389cedb140fb05e8063f48ef7eacc36
2023-12-01 17:29:42 +01:00
Zuul
9fb544d94a Merge "Fix port_security_enabled key for port module" 2023-10-17 09:28:52 +00:00
Simon Hensel
94ed95c8b6 Fix port_security_enabled key for port module
Changes to the port_security_enabled parameter are not applied due to
mismatching key names.
In the port module, the input parameter is called `port_security_enabled`,
while the OpenStackSDK is using a field called `is_port_security_enabled`.

When updating an existing port, the port module is comparing the dictionary
keys of the Ansible module parameters with those of the port object
returned by the OpenStackSDK.
Since these keys different, they will not match and changes to
port security are not applied.

Story: 2010687
Task: 47789
Change-Id: I838e9d6ebf1a281269add91724eac240abe35fd4
2023-10-17 08:56:21 +02:00
Zuul
4ab054790c Merge "Prevent routers to be always updated if no shared public network" 2023-10-16 15:31:30 +00:00
Zuul
2c68080758 Merge "Added module for volume type encription" 2023-10-16 14:42:15 +00:00
Zuul
6e680d594b Merge "Add volume_type related plugins/modules" 2023-10-16 14:29:57 +00:00
Dmitriy Rabotyagov
b25e93dbdd Prevent routers to be always updated if no shared public network
Current logic assumes that external_fixed_ips should be always defined,
otherwise `req_fip_map` is an empty sequence, which makes _needs_update
to return True.
With that not having external_fixed_ips is a vaild case whenever
deployment does not have shared public network. This usually
the case when public network is not passed to computes and public
network is used only for routers and floating IPs.

Patch changes logic by addind a `is not None` support to only compare
external_fip configration when user explicitly passed something (passing
an empty dict is equal to requesting "empty" configuration).

Co-Authored-by: Artem Goncharov
Change-Id: Id0f69fe4c985c4c38b493577250cad4e589b9d24
2023-10-16 12:04:50 +02:00
Will Szumski
0aedc268f1 Adds stateful parameter to security groups
This is a missing option.

Change-Id: Ic7b43093d9c35de8962978e9ee108cf7b5379fcd
2023-09-01 17:53:32 +00:00
Dmitriy Rabotyagov
9b47cb4b59 Fix usage of subnet_id key for router
At the moment `subnet` is an alias of `subnet_id`. The way, how aliases
work in ansible modules, is that ansible does add intended key to param
in case alias is used. When riginal key is used, aliases are not
populated.

Right now in case user define `subnet_id` instead of its alias `subnet`
module will fail with KeyError.

Change-Id: I5ce547352097ea821be4c9bbc18147575986c740
2023-09-01 07:23:05 +00:00
Zuul
8612171af3 Merge "Image filters should be dict not set" 2023-08-28 11:04:30 +00:00
arddennis
8f321eaeb2 Added module for volume type encription
New module to manipulate volume type encryption. Including simple CI
task to verify functionality.

Change-Id: I7380a5d258c3df1f9bd512aa4295868294391e31
2023-08-21 08:43:23 +02:00
Denys Mishchenko
147ad6c452 Add volume_type related plugins/modules
Added 2 new modules to manipulate volume types in OpenStack
* volume_type is used to create, delete and modify volume type
* volume_type_info is used to show volume_type details, including
  encryption information

ci tests extended with additional role to test basic module behaviour

It is currently impossible to update is_public volume type attribute
as it is being changed to "os-volume-type-access:is_public" which is not
expected by api. Which expects just "is_public"
https://docs.openstack.org/api-ref/block-storage/v3/?expanded=update-a-volume-type-detail#update-a-volume-type
Which results in "'os-volume-type-access:is_public' was unexpected"
reply. I guess the change is required by openstacksdk or on the API side

Change-Id: Idc26a5240b5f3314c8384c7326d8a82dcc8c6171
2023-08-16 16:35:51 +02:00
Dmitriy Rabotyagov
407369da6e Fix linters for mocking
Right now linters test fail due to a trivial issue in mock loader.
This aims to fix CI for the repo.

Change-Id: Ib58e70d3a54b75ca4cb9ad86b761db1ded157143
2023-08-15 20:16:31 +02:00
Dmitriy Rabotyagov
0a371445eb Image filters should be dict not set
At the moment we generate a set as a filter for image checksums which
lead to AttributeError in SDK:
'set' object has no attribute 'keys'

With that we ensure that supllying checksum does not cause
module crash.

Change-Id: I490f51950592f62c9ad81806593340779bf6dbdb
2023-07-25 12:49:28 +02:00
Joker 234
2808d1c155 fix(inventory): bug when using clouds_yaml_path
Before this fix the current implementation in combination with the most
recent openstacksdk (1.2.0) resulted in a list containing the default
values and another list inside this list containing the value of
clouds_yaml_path. The clouds_yaml_path value gets now added directly to
the list only if it was set.

Change-Id: I3c3b6f59393928d098e9b80c55b87fc6ee1e9912
2023-06-01 19:41:20 +02:00
Jakob Meng
c30e4db77c Publish 2.1.0 release
Change-Id: I142f14dd35720557539b9cf3489a3c12c31f342a
2023-04-18 08:46:14 +02:00
Jakob Meng
ab6f2e45c6 Change security group rules only when instructed to do so
Security group rules in module openstack.cloud.security_group
are changed/updated only when option 'security_group_rules' was
defined explicitly. This follows our policy of "apply no change"
when module options in our Ansible modules have not been set.

Story: 2010691
Task: 47795
Change-Id: I4a0cda46cb160b5321913b63ff1123d8b8a19705
2023-04-18 08:39:57 +02:00
Zuul
568adcb890 Merge "Add baremetal_deploy_template module" 2023-04-17 23:37:52 +00:00
Jakob Meng
18cf839db4 Highlight our mode of operation more prominently
Change-Id: I23d9f390ffbe2c67ebca4e114c9d417a8bd2065e
2023-03-30 10:02:46 +02:00
Mark Goddard
454a05452b Add baremetal_deploy_template module
This module supports managing deploy template resources in OpenStack
Ironic.

https: //docs.openstack.org/ironic/latest/admin/node-deployment.html#deploy-templates

Change-Id: I2d1b89e7cbd1a7e847f54ffd62778f953ba65863
2023-03-28 12:05:38 +00:00
Dmitriy Rabotyagov
ad9594dcd7 Fix mistake in compute_flavor_access notes
In order to execute addTenantAccess or removeTenantAccess
a flavor should have have is_public set to false, which means it must
be private.

Change-Id: Iea1c4e7167b7134a4f70a4fb44fc0a8676265419
2023-03-14 18:43:35 +01:00
Jakob Meng
62c0169e64 Fixed private option in inventory plugin
Story: 2010614
Task: 47538
Change-Id: I64e1b3ce5323ca8e351ee9faef4bddbef53dfd5d
2023-03-06 21:07:23 +01:00
Christian Kueppers
497f020100 Fix for AttributeError: 'dict' object has no attribute 'status'.
Story: 2010610
Task: 47505
Change-Id: I9e138d8f282de9adfb3d7e1142c10ab77c22578e
2023-03-05 09:41:42 +00:00
Jakob Meng
92c3e87467 Respect description option and delete security group rules first
The description option of security group rules will now be used properly
when creating new rules.

Security group rules have to be deleted first before new ones get
created, because if one changes one rule attribute such as its
description, then the old rule must be deleted before recreating it,
as rules cannot be updated.

Story: 2010605
Task: 47486

Change-Id: I75b900e6675f7ec33532089738a6c2bfc10a898b
2023-02-23 21:20:51 +01:00
Jakob Meng
f73a0e385e Use true and false instead of yes and no for boolean values
Story: 2010586
Task: 47380
Change-Id: I1b88aa925d823d74b2d012153dfe26d35c93dfd5
2023-02-21 13:07:29 +01:00
Jakob Meng
b6b5f63877 Do not hardcode image name and properly delete mock images in CI tests
Change-Id: I2962dd1d1c27989cbd135b051cfb3e9f8c9e3823
2023-02-21 13:06:44 +01:00
Sagi Shnaidman
edd4e1b2e9 Fix issue with multiple records in recordset
Story: #2010527
Task: #47136

Sort records in recordset so it can be compared to existing ones
and not to trigger update in case of a different order.

Change-Id: Ib5d2af56616532174c29ec2be86827ccd0a17940
2023-02-01 21:26:26 +02:00
132 changed files with 7058 additions and 573 deletions

View File

@@ -47,6 +47,8 @@
devstack_services:
designate: true
neutron-dns: true
neutron-trunk: true
neutron-segments: true
zuul_copy_output:
'{{ devstack_log_dir }}/test_output.log': 'logs'
extensions_to_txt:
@@ -94,6 +96,39 @@
c-bak: false
tox_extra_args: -vv --skip-missing-interpreters=false -- coe_cluster coe_cluster_template
- job:
name: ansible-collections-openstack-functional-devstack-manila-base
parent: ansible-collections-openstack-functional-devstack-base
# Do not restrict branches in base jobs because else Zuul would not find a matching
# parent job variant during job freeze when child jobs are on other branches.
description: |
Run openstack collections functional tests against a devstack with Manila plugin enabled
# Do not set job.override-checkout or job.required-projects.override-checkout in base job because
# else Zuul will use this branch when matching variants for parent jobs during job freeze
required-projects:
- openstack/manila
- openstack/python-manilaclient
files:
- ^ci/roles/share_type/.*$
- ^plugins/modules/share_type.py
- ^plugins/modules/share_type_info.py
timeout: 10800
vars:
devstack_localrc:
MANILA_ENABLED_BACKENDS: generic
MANILA_OPTGROUP_generic_driver_handles_share_servers: true
MANILA_OPTGROUP_generic_connect_share_server_to_tenant_network: true
MANILA_USE_SERVICE_INSTANCE_PASSWORD: true
devstack_plugins:
manila: https://opendev.org/openstack/manila
devstack_services:
manila: true
m-api: true
m-sch: true
m-shr: true
m-dat: true
tox_extra_args: -vv --skip-missing-interpreters=false -- share_type share_type_info
- job:
name: ansible-collections-openstack-functional-devstack-magnum
parent: ansible-collections-openstack-functional-devstack-magnum-base
@@ -103,6 +138,15 @@
with Magnum plugin enabled, using master of openstacksdk and latest
ansible release. Run it only on coe_cluster{,_template} changes.
- job:
name: ansible-collections-openstack-functional-devstack-manila
parent: ansible-collections-openstack-functional-devstack-manila-base
branches: master
description: |
Run openstack collections functional tests against a master devstack
with Manila plugin enabled, using master of openstacksdk and latest
ansible release. Run it only on share_type{,_info} changes.
- job:
name: ansible-collections-openstack-functional-devstack-octavia-base
parent: ansible-collections-openstack-functional-devstack-base
@@ -162,45 +206,18 @@
tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-1.x.x.txt'
tox_install_siblings: false
# Job with Ansible 2.9 for checking backward compatibility
- job:
name: ansible-collections-openstack-functional-devstack-ansible-2.9
name: ansible-collections-openstack-functional-devstack-ansible-2.18
parent: ansible-collections-openstack-functional-devstack-base
branches: master
description: |
Run openstack collections functional tests against a master devstack
using master of openstacksdk and stable 2.9 branch of ansible
using master of openstacksdk and stable 2.16 branch of ansible
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.9
override-checkout: stable-2.18
vars:
tox_envlist: ansible_2_9
- job:
name: ansible-collections-openstack-functional-devstack-ansible-2.11
parent: ansible-collections-openstack-functional-devstack-base
branches: master
description: |
Run openstack collections functional tests against a master devstack
using master of openstacksdk and stable 2.12 branch of ansible
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.11
vars:
tox_envlist: ansible_2_11
- job:
name: ansible-collections-openstack-functional-devstack-ansible-2.12
parent: ansible-collections-openstack-functional-devstack-base
branches: master
description: |
Run openstack collections functional tests against a master devstack
using master of openstacksdk and stable 2.12 branch of ansible
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.12
vars:
tox_envlist: ansible_2_12
tox_envlist: ansible_2_18
- job:
name: ansible-collections-openstack-functional-devstack-ansible-devel
@@ -244,24 +261,22 @@
bindep_profile: test py310
- job:
name: openstack-tox-linters-ansible-2.12
name: openstack-tox-linters-ansible-2.18
parent: openstack-tox-linters-ansible
nodeset: ubuntu-focal
description: |
Run openstack collections linter tests using the 2.12 branch of ansible
Run openstack collections linter tests using the 2.18 branch of ansible
required-projects:
- name: github.com/ansible/ansible
override-checkout: stable-2.12
override-checkout: stable-2.18
vars:
ensure_tox_version: '<4'
tox_envlist: linters_2_12
python_version: 3.8
bindep_profile: test py38
tox_envlist: linters_2_18
python_version: "3.12"
bindep_profile: test py312
# Cross-checks with other projects
- job:
name: bifrost-collections-src
parent: bifrost-integration-tinyipa-ubuntu-focal
parent: bifrost-integration-on-ubuntu-jammy
required-projects:
- openstack/ansible-collections-openstack
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
@@ -272,7 +287,7 @@
override-checkout: master
- job:
name: bifrost-keystone-collections-src
parent: bifrost-integration-tinyipa-keystone-ubuntu-focal
parent: bifrost-integration-keystone-on-ubuntu-jammy
required-projects:
- openstack/ansible-collections-openstack
- # always use master branch when collecting parent job variants, refer to git blame for rationale.
@@ -284,7 +299,7 @@
- job:
name: ansible-collections-openstack-release
parent: base
parent: openstack-tox-linters-ansible
run: ci/publish/publish_collection.yml
secrets:
- ansible_galaxy_info
@@ -294,79 +309,61 @@
data:
url: https://galaxy.ansible.com
token: !encrypted/pkcs1-oaep
- lZFzfoCbuwqV1k6qRfl/VS7E+knUW7+zpg7BptrenK4n0g7UY0HtdVkYq0pV0Tj/LbhzG
jHD0mehcV1iS6B7ORKg4criJkdDfEx09BD8z8yv0EleiIMmhlrCoMcY593OZMBtVbGi0D
CwQtNO98QIsfZogChfLfvRNiBmUV98mEb/p6p3EtGx8J7qcAsqfWxc/CzB8GCleLAHHHT
FuikMM03ZnV0ew7E+TPkHbzzPhBZOqS5HYF0HtgttHwIXdfIWp/XdTuEEk7uRRgYZ2Iao
ifWRzoKaOQmhM++e1ydCqw9D4y9dZEFNMQLwSqcrvtb8cNwT1kl7SCFqYNE2lbutj4ne6
PTBQRsKegMB4Y3ena14fNF6tCynvJLPhF/cjPH2Jhs+B19XQhWkL3TgiOY02W24YHwRcP
+LdkM8inAvyVi3DEbEqdjBPO9OFJcBOKPlCdkGvuwdNCuEpEwctWs0gV3voflG2CDKzmJ
wu9JJOAWnq/0l1WpuDqWreKeQ/BUGZC2Gb4xRAqofulgvhs4WuYoEccjH4EJFIZ90S1EP
R/ZLadqZaEhmjwGM5sMWbBbjT23XsRgg0Tzt9m8DENYMuYDqkMdRbt2jYZa+32p4hyxVe
Y6H/pqYq5b9uOzumnShaK4WlmkQyXcNPkoSlMC1h4OGvqX/WUixpI38jyMA5Tc=
- K93hOZo1B5z248H04COB1N2HCkGbFPo2EUr+0W7qFzsrdvmbsAI86Hl9bUCfEENGrwvfV
0j9CE5iO0tyqal3r6ucMhGT44MgQWL3MBeRvK89yAJpSNMU7R7rEY/zbjZMoC9YElcHEv
GEDZSA/0gQHCHpZVDlx4JMGwrnd+Nz9ha3c12BYeZS8rS/dQl7EmZ867OsozmNdG9UkkC
0vP/dkenUQNvoZOSWgZztRBlbAyI1nc5iEEw9vvpLh19HcY9+S2iAZkgSq4jOOO4wn7gE
XAZPr0HRdwS2m4Hw0Pusrg7SdC3+2O0N/fvFGnvvKXHcSgQk3rPLn6HfKzOJoPWc4WlDX
MA79jYloNBXjOaeXOoiwYzzshWK53F6Ci+3leq1cYuFyHSi2ds2mYXat7YndZSsmsk5um
hj0+Ddy9Om1uYy3nhHyZLULE7UDUmduA9EPkvdyWlcW0yZL2kXcrDTHlSp4PaJg9iKVys
0aOOo9CNMwhyXAOGiFCYF/m7Efbnp50zUQhHN9+7LeVzXZuiH98C8kNvWfE0qrkrrgQ1n
78UMqGcGpdw4ZSlWrDTbrbd4v0bRnsJ+IAWISnT5OXaeJgGZwXRuBHtTXqbjoosBeX/8w
YKb0lx7E5ZtSw7+Y6LNDGihGTmVg1nkZUWo85CxyF/RiWHuNvpkzzqXmdGS1bg=
- project:
check:
jobs:
- tox-pep8
- openstack-tox-linters-ansible-devel
- openstack-tox-linters-ansible-2.12
- ansible-collections-openstack-functional-devstack:
dependencies: &deps_unit_lint
- tox-pep8
- openstack-tox-linters-ansible-2.12
- ansible-collections-openstack-functional-devstack-releases:
dependencies: *deps_unit_lint
- ansible-collections-openstack-functional-devstack-ansible-2.9:
dependencies: *deps_unit_lint
- ansible-collections-openstack-functional-devstack-ansible-2.12:
dependencies: *deps_unit_lint
- ansible-collections-openstack-functional-devstack-ansible-devel:
dependencies: *deps_unit_lint
- ansible-collections-openstack-functional-devstack-magnum:
dependencies: *deps_unit_lint
- ansible-collections-openstack-functional-devstack-octavia:
dependencies: *deps_unit_lint
- openstack-tox-linters-ansible-2.18
- ansible-collections-openstack-functional-devstack
- ansible-collections-openstack-functional-devstack-releases
- ansible-collections-openstack-functional-devstack-ansible-2.18
- ansible-collections-openstack-functional-devstack-ansible-devel
- ansible-collections-openstack-functional-devstack-magnum
- ansible-collections-openstack-functional-devstack-manila
- ansible-collections-openstack-functional-devstack-octavia
- 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:
- tox-pep8
- openstack-tox-linters-ansible-2.12
# - ansible-collections-openstack-functional-devstack
- openstack-tox-linters-ansible-2.18
- ansible-collections-openstack-functional-devstack-releases
# - ansible-collections-openstack-functional-devstack-ansible-2.9
# - ansible-collections-openstack-functional-devstack-ansible-2.12
- ansible-collections-openstack-functional-devstack-magnum
- ansible-collections-openstack-functional-devstack-manila
- ansible-collections-openstack-functional-devstack-octavia
periodic:
jobs:
- openstack-tox-linters-ansible-devel
- openstack-tox-linters-ansible-2.12
- openstack-tox-linters-ansible-2.18
- ansible-collections-openstack-functional-devstack
- ansible-collections-openstack-functional-devstack-releases
- ansible-collections-openstack-functional-devstack-ansible-2.9
- ansible-collections-openstack-functional-devstack-ansible-2.12
- ansible-collections-openstack-functional-devstack-ansible-2.18
- ansible-collections-openstack-functional-devstack-ansible-devel
- bifrost-collections-src
- bifrost-keystone-collections-src
- ansible-collections-openstack-functional-devstack-magnum
- ansible-collections-openstack-functional-devstack-manila
- ansible-collections-openstack-functional-devstack-octavia
experimental:
jobs:
- ansible-collections-openstack-functional-devstack-ansible-2.11
tag:
jobs:
- ansible-collections-openstack-release

View File

@@ -4,6 +4,228 @@ Ansible OpenStack Collection Release Notes
.. contents:: Topics
v2.5.0
======
Release Summary
---------------
Bugfixes and minor changes
Major Changes
-------------
- Add import_method to module
- Add object_containers_info module
- Add support for filters in inventory
- Add volume_manage module
- Introduce share_type modules
Minor Changes
-------------
- Allow role_assignment module to work cross domain
- Don't compare current state for `reboot_*` actions
- Fix disable_gateway_ip for subnet
- Fix example in the dns_zone_info module doc
- Fix router module external IPs when only subnet specified
- Fix the bug reporting url
- Let clouds_yaml_path behave as documented (Override path to clouds.yaml file)
- Shows missing data in `stack_info` module output
v2.4.1
======
Release Summary
---------------
Bugfixes and minor changes
Minor Changes
-------------
- Update tags when changing server
Bugfixes
--------
- Fix missed client_cert in OpenStackModule
v2.4.0
======
Release Summary
---------------
New trait module and minor changes
Major Changes
-------------
- Add trait module
Minor Changes
-------------
- Add loadbalancer quota options
- Allow create instance with tags
New Modules
-----------
- openstack.cloud.trait - Add or Delete a trait from OpenStack
v2.3.3
======
Release Summary
---------------
Bugfixes and minor changes
Minor Changes
-------------
- Add test to only_ipv4 in inventory
- add an option to use only IPv4 only for ansible_host and ansible_ssh_host
Bugfixes
--------
- CI - Fix deprecated ANSIBLE_COLLECTIONS_PATHS variable
v2.3.2
======
Release Summary
---------------
Bugfixes and minor changes
Minor Changes
-------------
- Drop compat implementations for tests
Bugfixes
--------
- Fix openstack.cloud.port module failure in check mode
v2.3.1
======
Release Summary
---------------
Client TLS certificate support
Minor Changes
-------------
- Add ability to pass client tls certificate
v2.3.0
======
Release Summary
---------------
Bugfixes and new modules
Major Changes
-------------
- Add Neutron trunk module
- Add application_credential module
- Add module to filter available volume services
Minor Changes
-------------
- Add inactive state for the images
- Add insecure_registry property to coe_cluster_templates
- Add support for creation of the default external networks
- Add target_all_project option
- Add vlan_tranparency for creation networks
- Allow munch results in server_info module
- Allow to specify multiple allocation pools when creating a subnet
- CI - Disable auto-discovery for setuptools
- CI - Don't create port with binding profile
- CI - Fix CI in collection
- CI - Fix linters-devel and devstack tests
- CI - Fix regression in quota module
- CI - Fix test for server shelve
- CI - Migrate Bifrost jobs to Ubuntu Jammy
- CI - Remove 2.9 jobs from Zuul config
- CI - Run functional testing regardless of pep8/linter results
- Enable glance-direct interop image import
- Ensure coe_cluster_template compare labels properly
- Wait for deleted server to disappear from results
- router - Allow specifying external network name in a different project
Bugfixes
--------
- Allow wait false when auto_ip is false
- Fix exception when creating object from file
- Fix exception when updating container with metadata
- Fix typo in openstack.cloud.lb_pool
- Fix typo in parameter description
- fix subnet module - allow cidr option with subnet_pool
New Modules
-----------
- openstack.cloud.application_credential - Manage OpenStack Identity (Keystone) application credentials
- openstack.cloud.trunk - Add or delete trunks from an OpenStack cloud
- openstack.cloud.volume_service_info - Fetch OpenStack Volume (Cinder) services
v2.2.0
======
Release Summary
---------------
New module for volume_type and bugfixes
Minor Changes
-------------
- Add volume_encryption_type modules
- Add volume_type modules
Bugfixes
--------
- Fix image module filter
- Fix port module idempotency
- Fix router module idempotency
v2.1.0
======
Release Summary
---------------
New module for Ironic and bugfixes
Minor Changes
-------------
- Add baremetal_deploy_template module
- Highlight our mode of operation more prominently
Bugfixes
--------
- Change security group rules only when instructed to do so
- Fix for AttributeError: 'dict' object has no attribute 'status'
- Fix issue with multiple records in recordset
- Fix mistake in compute_flavor_access notes
- Fixed private option in inventory plugin
- Respect description option and delete security group rules first
- Use true and false instead of yes and no for boolean values
v2.0.0
======

View File

@@ -6,6 +6,12 @@ http://zuul.opendev.org/t/openstack/builds?project=openstack%2Fansible-collectio
Ansible OpenStack collection aka `openstack.cloud` provides Ansible modules and Ansible plugins for managing OpenStack
clouds. It is supported and maintained by the OpenStack community.
**NOTE:** We need and value your contributions! Maintaining this collection is a community effort. We are all both users
and developers of this collection at the same time. If you find a bug, please report it. If you have fixed a bug, please
submit a patch. If you need new functionality which is not covered by this collection yet, please extend an existing
module or submit a new one. Our [Contributing](#contributing) section below has tons of docs to check out. Please get in
touch!
## Branches and Non Backward Compatibility ⚠️
Our codebase has been split into two separate release series, `2.x.x` and `1.x.x`:
@@ -205,7 +211,7 @@ Thank you for your interest in our Ansible OpenStack collection ☺️
There are many ways in which you can participate in the project, for example:
- [Report and verify bugs and help with solving issues](
https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack).
https://bugs.launchpad.net/ansible-collections-openstack).
- [Submit and review patches](
https://review.opendev.org/#/q/project:openstack/ansible-collections-openstack).
- Follow OpenStack's [How To Contribute](https://wiki.openstack.org/wiki/How_To_Contribute) guide.
@@ -216,8 +222,7 @@ docs/reviewing.md) (⚠️) before sending your first patch. Pull requests submi
## Communication
We have a Special Interest Group for the Ansible OpenStack collection. Join us in `#openstack-ansible-sig` on
[OFTC IRC](https://www.oftc.net/) and ping Artem Goncharov <artem.goncharov@gmail.com> (gtema), Jakob Meng
<mail@jakobmeng.de> (jm1) or Sagi Shnaidman <sshnaidm@redhat.com> (sshnaidm) 🍪
[OFTC IRC](https://www.oftc.net/) 🍪
## License

View File

@@ -500,3 +500,138 @@ releases:
directory which could be used to generate and develop new Ansible modules
for this collection have been removed.
release_date: '2023-01-31'
2.1.0:
changes:
bugfixes:
- Change security group rules only when instructed to do so
- 'Fix for AttributeError: ''dict'' object has no attribute ''status'''
- Fix issue with multiple records in recordset
- Fix mistake in compute_flavor_access notes
- Fixed private option in inventory plugin
- Respect description option and delete security group rules first
- Use true and false instead of yes and no for boolean values
minor_changes:
- Add baremetal_deploy_template module
- Highlight our mode of operation more prominently
release_summary: New module for Ironic and bugfixes
release_date: '2023-04-19'
2.2.0:
changes:
bugfixes:
- Fix image module filter
- Fix port module idempotency
- Fix router module idempotency
minor_changes:
- Add volume_encryption_type modules
- Add volume_type modules
release_summary: New module for volume_type and bugfixes
release_date: '2023-12-01'
2.3.0:
changes:
bugfixes:
- Allow wait false when auto_ip is false
- Fix exception when creating object from file
- Fix exception when updating container with metadata
- Fix typo in openstack.cloud.lb_pool
- Fix typo in parameter description
- fix subnet module - allow cidr option with subnet_pool
major_changes:
- Add Neutron trunk module
- Add application_credential module
- Add module to filter available volume services
minor_changes:
- Add inactive state for the images
- Add insecure_registry property to coe_cluster_templates
- Add support for creation of the default external networks
- Add target_all_project option
- Add vlan_tranparency for creation networks
- Allow munch results in server_info module
- Allow to specify multiple allocation pools when creating a subnet
- CI - Disable auto-discovery for setuptools
- CI - Don't create port with binding profile
- CI - Fix CI in collection
- CI - Fix linters-devel and devstack tests
- CI - Fix regression in quota module
- CI - Fix test for server shelve
- CI - Migrate Bifrost jobs to Ubuntu Jammy
- CI - Remove 2.9 jobs from Zuul config
- CI - Run functional testing regardless of pep8/linter results
- Enable glance-direct interop image import
- Ensure coe_cluster_template compare labels properly
- Wait for deleted server to disappear from results
- router - Allow specifying external network name in a different project
release_summary: Bugfixes and new modules
modules:
- description: Manage OpenStack Identity (Keystone) application credentials
name: application_credential
namespace: ''
- description: Add or delete trunks from an OpenStack cloud
name: trunk
namespace: ''
- description: Fetch OpenStack Volume (Cinder) services
name: volume_service_info
namespace: ''
release_date: '2024-11-28'
2.3.1:
changes:
minor_changes:
- Add ability to pass client tls certificate
release_summary: Client TLS certificate support
release_date: '2024-12-18'
2.3.2:
changes:
bugfixes:
- Fix openstack.cloud.port module failure in check mode
minor_changes:
- Drop compat implementations for tests
release_summary: Bugfixes and minor changes
release_date: '2024-12-20'
2.3.3:
changes:
bugfixes:
- CI - Fix deprecated ANSIBLE_COLLECTIONS_PATHS variable
minor_changes:
- Add test to only_ipv4 in inventory
- add an option to use only IPv4 only for ansible_host and ansible_ssh_host
release_summary: Bugfixes and minor changes
release_date: '2024-12-22'
2.4.0:
changes:
major_changes:
- Add trait module
minor_changes:
- Add loadbalancer quota options
- Allow create instance with tags
release_summary: New trait module and minor changes
modules:
- description: Add or Delete a trait from OpenStack
name: trait
namespace: ''
release_date: '2025-01-15'
2.4.1:
changes:
bugfixes:
- Fix missed client_cert in OpenStackModule
minor_changes:
- Update tags when changing server
release_summary: Bugfixes and minor changes
release_date: '2024-01-20'
2.5.0:
changes:
major_changes:
- Add import_method to module
- Add object_containers_info module
- Add support for filters in inventory
- Add volume_manage module
- Introduce share_type modules
minor_changes:
- Allow role_assignment module to work cross domain
- Don't compare current state for `reboot_*` actions
- Fix disable_gateway_ip for subnet
- Fix example in the dns_zone_info module doc
- Fix router module external IPs when only subnet specified
- Fix the bug reporting url
- Let clouds_yaml_path behave as documented (Override path to clouds.yaml file)
- Shows missing data in `stack_info` module output
release_summary: Bugfixes and minor changes
release_date: '2025-10-24'

View File

@@ -0,0 +1,5 @@
---
minor_changes:
- Added the new ``openstack.cloud.baremetal_port_group`` module to manage
Bare Metal port groups (create, update, and delete), including CI role
coverage and unit tests.

View File

@@ -3,7 +3,8 @@
vars:
collection_path: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}"
build_collection_path: /tmp/collection_built/
ansible_galaxy_path: "~/.local/bin/ansible-galaxy"
ansible_virtualenv_path: /tmp/ansible_venv
ansible_galaxy_path: "{{ ansible_virtualenv_path }}/bin/ansible-galaxy"
tasks:
@@ -11,9 +12,15 @@
include_role:
name: ensure-pip
- name: Install ansible
- name: Install Ansible in virtualenv
pip:
name: ansible-core<2.12
name: ansible-core<2.19
virtualenv: "{{ ansible_virtualenv_path }}"
virtualenv_command: "{{ ensure_pip_virtualenv_command }}"
- name: Detect ansible version
command: "{{ ansible_virtualenv_path }}/bin/ansible --version"
register: ansible_version
- name: Discover tag version
set_fact:

View File

@@ -0,0 +1,9 @@
expected_fields:
- description
- expires_at
- id
- name
- project_id
- roles
- secret
- unrestricted

View File

@@ -0,0 +1,61 @@
---
- name: Create application credentials
openstack.cloud.application_credential:
cloud: "{{ cloud }}"
state: present
name: ansible_creds
description: dummy description
register: appcred
- name: Assert return values of application_credential module
assert:
that:
- appcred is changed
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(appcred.application_credential.keys())|length == 0
- name: Create the application credential again
openstack.cloud.application_credential:
cloud: "{{ cloud }}"
state: present
name: ansible_creds
description: dummy description
register: appcred
- name: Assert return values of ansible_credential module
assert:
that:
# credentials are immutable so creating twice will cause delete and create
- appcred is changed
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(appcred.application_credential.keys())|length == 0
- name: Update the application credential again
openstack.cloud.application_credential:
cloud: "{{ cloud }}"
state: present
name: ansible_creds
description: new description
register: appcred
- name: Assert application credential changed
assert:
that:
- appcred is changed
- appcred.application_credential.description == 'new description'
- name: Get list of all keypairs using application credential
openstack.cloud.keypair_info:
cloud: "{{ appcred.cloud }}"
- name: Delete application credential
openstack.cloud.application_credential:
cloud: "{{ cloud }}"
state: absent
name: ansible_creds
register: appcred
- name: Assert application credential changed
assert:
that: appcred is changed

View File

@@ -0,0 +1,7 @@
expected_fields:
- created_at
- extra
- id
- name
- steps
- updated_at

View File

@@ -0,0 +1,58 @@
---
# TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled.
- name: Create baremetal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: present
name: CUSTOM_ANSIBLE_DEPLOY_TEMPLATE
steps:
- interface: bios
step: apply_configuration
args:
settings:
- name: some-setting
value: some-value
priority: 110
register: template
- debug: var=template
- name: Assert return values of baremetal_deploy_template module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(template.template.keys())|length == 0
- name: Update baremetal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: present
id: "{{ template.template.id }}"
extra:
foo: bar
register: updated_template
- name: Assert return values of updated baremetal deploy template
assert:
that:
- updated_template is changed
- updated_template.template.id == template.template.id
- name: Update baremetal deploy template again
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: present
id: "{{ template.template.id }}"
register: updated_template
- name: Assert return values of updated baremetal deploy template
assert:
that:
- updated_template is not changed
- updated_template.template.id == template.template.id
- name: Delete Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: "{{ cloud }}"
state: absent
id: "{{ template.template.id }}"

View File

@@ -0,0 +1,12 @@
expected_fields:
- address
- created_at
- extra
- id
- links
- mode
- name
- node_id
- properties
- standalone_ports_supported
- updated_at

View File

@@ -0,0 +1,100 @@
---
# TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled.
- name: Create baremetal node
openstack.cloud.baremetal_node:
cloud: "{{ cloud }}"
driver_info:
ipmi_address: "1.2.3.4"
ipmi_username: "admin"
ipmi_password: "secret"
name: ansible_baremetal_node
nics:
- mac: "aa:bb:cc:aa:bb:cc"
state: present
register: node
- name: Create baremetal port group
openstack.cloud.baremetal_port_group:
cloud: "{{ cloud }}"
state: present
name: ansible_baremetal_port_group
node: ansible_baremetal_node
address: fa:16:3e:aa:aa:ab
mode: active-backup
standalone_ports_supported: true
extra:
test: created
properties:
miimon: '100'
register: port_group
- debug: var=port_group
- name: Assert return values of baremetal_port_group module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(port_group.port_group.keys())|length == 0
- port_group.port_group.name == "ansible_baremetal_port_group"
- port_group.port_group.node_id == node.node.id
- name: Update baremetal port group
openstack.cloud.baremetal_port_group:
cloud: "{{ cloud }}"
state: present
id: "{{ port_group.port_group.id }}"
mode: 802.3ad
standalone_ports_supported: false
extra:
test: updated
register: updated_port_group
- name: Assert return values of updated baremetal port group
assert:
that:
- updated_port_group is changed
- updated_port_group.port_group.id == port_group.port_group.id
- updated_port_group.port_group.mode == "802.3ad"
- not updated_port_group.port_group.standalone_ports_supported
- updated_port_group.port_group.extra.test == "updated"
- name: Update baremetal port group again
openstack.cloud.baremetal_port_group:
cloud: "{{ cloud }}"
state: present
id: "{{ port_group.port_group.id }}"
mode: 802.3ad
standalone_ports_supported: false
extra:
test: updated
register: updated_port_group
- name: Assert idempotency for baremetal port group module
assert:
that:
- updated_port_group is not changed
- updated_port_group.port_group.id == port_group.port_group.id
- name: Delete baremetal port group
openstack.cloud.baremetal_port_group:
cloud: "{{ cloud }}"
state: absent
id: "{{ port_group.port_group.id }}"
- name: Delete baremetal port group again
openstack.cloud.baremetal_port_group:
cloud: "{{ cloud }}"
state: absent
id: "{{ port_group.port_group.id }}"
register: deleted_port_group
- name: Assert idempotency for deleted baremetal port group
assert:
that:
- deleted_port_group is not changed
- name: Delete baremetal node
openstack.cloud.baremetal_node:
cloud: "{{ cloud }}"
name: ansible_baremetal_node
state: absent

View File

@@ -72,6 +72,8 @@
image_id: '{{ image_id }}'
is_floating_ip_enabled: true
keypair_id: '{{ keypair.keypair.id }}'
flavor_id: 'm1.small'
master_flavor_id: 'm1.small'
name: k8s
state: present
register: coe_cluster_template

View File

@@ -26,6 +26,9 @@
keypair_id: '{{ keypair.keypair.id }}'
name: k8s
state: present
labels:
docker_volume_size: 10
cloud_provider_tag: v1.23.1
register: coe_cluster_template
- name: Assert return values of coe_cluster_template module
@@ -43,6 +46,9 @@
keypair_id: '{{ keypair.keypair.id }}'
name: k8s
state: present
labels:
docker_volume_size: 10
cloud_provider_tag: v1.23.1
register: coe_cluster_template
- name: Assert return values of coe_cluster_template module

View File

@@ -19,7 +19,7 @@
name: '{{ mapping_name }}'
rules: '{{ mapping_rules_1 }}'
register: create_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -91,7 +91,7 @@
name: '{{ mapping_name }}'
rules: '{{ mapping_rules_1 }}'
register: create_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -117,7 +117,7 @@
name: '{{ mapping_name }}'
rules: '{{ mapping_rules_2 }}'
register: update_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -196,7 +196,7 @@
state: 'absent'
name: '{{ mapping_name }}'
register: delete_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -217,7 +217,7 @@
state: 'absent'
name: '{{ mapping_name }}'
register: delete_mapping
check_mode: yes
check_mode: true
- assert:
that:
@@ -257,10 +257,10 @@
openstack.cloud.federation_mapping:
state: 'absent'
name: '{{ mapping_name }}'
ignore_errors: yes
ignore_errors: true
- name: 'Delete second mapping'
openstack.cloud.federation_mapping:
state: 'absent'
name: '{{ mapping_name_2 }}'
ignore_errors: yes
ignore_errors: true

View File

@@ -214,7 +214,7 @@
cloud: "{{ cloud }}"
state: present
server: ansible_server1
wait: yes
wait: true
- name: Get floating ip attached to server 1
openstack.cloud.floating_ip_info:
@@ -241,7 +241,7 @@
that:
- server1_fips is success
- server1_fips is not changed
- server1_fips.floating_ips
- server1_fips.floating_ips|length > 0
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(server1_fips.floating_ips[0].keys())|length == 0
@@ -260,7 +260,7 @@
- name: Assert return values of floating_ip module
assert:
that:
- floating_ip.floating_ip
- floating_ip.floating_ip|length > 0
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(floating_ip.floating_ip.keys())|length == 0
@@ -302,7 +302,7 @@
openstack.cloud.floating_ip:
cloud: "{{ cloud }}"
state: present
reuse: no # else fixed_address will be ignored
reuse: false # else fixed_address will be ignored
server: ansible_server2
network: public
fixed_address: "{{ port2.port.fixed_ips[0].ip_address }}"
@@ -312,7 +312,7 @@
- name: Assert floating ip attached to server 2
assert:
that:
- server2_fip.floating_ip
- server2_fip.floating_ip|length > 0
- name: Find all floating ips for debugging
openstack.cloud.floating_ip_info:
@@ -347,12 +347,12 @@
openstack.cloud.floating_ip:
cloud: "{{ cloud }}"
state: present
reuse: no # else fixed_address will be ignored
reuse: false # else fixed_address will be ignored
server: ansible_server2
network: ansible_external
fixed_address: "{{ port3.port.fixed_ips[0].ip_address }}"
floating_ip_address: "10.6.6.150"
wait: no # does not work anyway and causes issues in local testing
wait: false # does not work anyway and causes issues in local testing
- name: Get floating ip attached to server 2
openstack.cloud.floating_ip_info:

View File

@@ -28,7 +28,7 @@
domain: default
default_project: demo
register: user
ignore_errors: yes
ignore_errors: true
- name: Assert that update failed
assert:
@@ -93,7 +93,7 @@
update_password: always
email: updated.ansible.user@nowhere.net
register: user
ignore_errors: yes
ignore_errors: true
- name: Assert user update failed
assert:

View File

@@ -24,6 +24,13 @@
path: '{{ tmp_file.path }}'
size: 1M
- name: Calculating file checksum
ansible.builtin.stat:
path: "{{ tmp_file.path }}"
checksum_algorithm: sha512
get_checksum: true
register: image_details
- name: Ensure mock kernel and ramdisk images (defaults)
openstack.cloud.image:
cloud: "{{ cloud }}"
@@ -42,6 +49,7 @@
name: ansible_image
filename: "{{ tmp_file.path }}"
is_protected: true
checksum: "{{ image_details.stat.checksum }}"
disk_format: raw
tags:
- test
@@ -168,6 +176,34 @@
- image is changed
- image.image.name == 'ansible_image-changed'
- name: Deactivate raw image
openstack.cloud.image:
cloud: "{{ cloud }}"
state: inactive
id: "{{ image.image.id }}"
name: 'ansible_image-changed'
register: image
- name: Assert changed
assert:
that:
- image is changed
- image.image.status == 'deactivated'
- name: Reactivate raw image
openstack.cloud.image:
cloud: "{{ cloud }}"
state: present
id: "{{ image.image.id }}"
name: 'ansible_image-changed'
register: image
- name: Assert changed
assert:
that:
- image is changed
- image.image.status == 'active'
- name: Rename back raw image (defaults)
openstack.cloud.image:
cloud: "{{ cloud }}"
@@ -318,6 +354,15 @@
name: ansible_project
domain: default
- name: Delete mock kernel and ramdisk images
openstack.cloud.image:
cloud: "{{ cloud }}"
state: absent
name: "{{ item }}"
loop:
- cirros-vmlinuz
- cirros-initrd
- name: Delete test image file
file:
name: "{{ tmp_file.path }}"

View File

@@ -279,6 +279,11 @@
ansible.builtin.set_fact:
cache: "{{ cache.content | b64decode | from_yaml }}"
- name: Further process Ansible 2.19+ cache
ansible.builtin.set_fact:
cache: "{{ cache.__payload__ | from_yaml }}"
when: cache.__payload__ is defined
- name: Check Ansible's cache
assert:
that:
@@ -303,6 +308,25 @@
that:
- inventory.all.children.RegionOne.hosts.keys() | sort == ['ansible_server1', 'ansible_server2'] | sort
- name: List servers with inventory plugin with IPv4 only
ansible.builtin.command:
cmd: ansible-inventory --list --yaml --extra-vars only_ipv4=true --inventory-file openstack.yaml
chdir: "{{ tmp_dir.path }}"
environment:
ANSIBLE_INVENTORY_CACHE: "True"
ANSIBLE_INVENTORY_CACHE_PLUGIN: "jsonfile"
ANSIBLE_CACHE_PLUGIN_CONNECTION: "{{ tmp_dir.path }}/.cache/"
register: inventory
- name: Read YAML output from inventory plugin again
ansible.builtin.set_fact:
inventory: "{{ inventory.stdout | from_yaml }}"
- name: Check YAML output from inventory plugin again
assert:
that:
- inventory.all.children.RegionOne.hosts.keys() | sort == ['ansible_server1', 'ansible_server2'] | sort
- name: Delete server 2
openstack.cloud.resource:
service: compute

View File

@@ -38,7 +38,7 @@
- name: Ensure public key is returned
assert:
that:
- keypair.keypair.public_key is defined and keypair.keypair.public_key
- keypair.keypair.public_key is defined and keypair.keypair.public_key|length > 0
- name: Create another keypair
openstack.cloud.keypair:
@@ -78,7 +78,7 @@
- name: Generate test key file
user:
name: "{{ ansible_env.USER }}"
generate_ssh_key: yes
generate_ssh_key: true
ssh_key_file: .ssh/shade_id_rsa
- name: Create keypair (file)

View File

@@ -78,7 +78,7 @@
# Creation
- name: Create protocol - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -119,7 +119,7 @@
- expected_fields|difference(protocol.protocol.keys())|length == 0
- name: Create protocol (retry - no change) - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -148,7 +148,7 @@
# Update
- name: Update protocol - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -174,7 +174,7 @@
- protocol.protocol.mapping_id == 'ansible_mapping2'
- name: Update protocol (retry - no change) - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: present
name: ansible_protocol1
@@ -258,7 +258,7 @@
# Deletion
- name: Delete protocol - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: absent
name: ansible_protocol1
@@ -279,7 +279,7 @@
- protocol is changed
- name: Delete protocol (retry - no change) - CHECK MODE
check_mode: yes
check_mode: true
openstack.cloud.keystone_federation_protocol:
state: absent
name: ansible_protocol1
@@ -307,35 +307,35 @@
state: absent
name: ansible_protocol1
idp_id: ansible_idp
ignore_errors: yes
ignore_errors: true
- name: Delete protocol (2)
openstack.cloud.keystone_federation_protocol:
state: absent
name: ansible_protocol2
idp_id: ansible_idp
ignore_errors: yes
ignore_errors: true
- name: Delete mapping 1
openstack.cloud.federation_mapping:
state: absent
name: ansible_mapping1
ignore_errors: yes
ignore_errors: true
- name: Delete mapping 2
openstack.cloud.federation_mapping:
state: absent
name: ansible_mapping2
ignore_errors: yes
ignore_errors: true
- name: Delete idp
openstack.cloud.federation_idp:
state: absent
name: ansible_idp
ignore_errors: yes
ignore_errors: true
- name: Delete domain
openstack.cloud.identity_domain:
state: absent
name: ansible_domain
ignore_errors: yes
ignore_errors: true

View File

@@ -116,7 +116,7 @@
_idps: '{{ idps.identity_providers }}'
- name: 'Create identity_provider (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -150,7 +150,7 @@
# Update (simple cases)
- name: 'Update IDP set description - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -181,7 +181,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set description (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -213,7 +213,7 @@
- name: 'Update IDP set Remote IDs - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -244,7 +244,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set Remote IDs (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -275,7 +275,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set Enabled - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -306,7 +306,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update IDP set Enabled (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -338,7 +338,7 @@
# If we don't specify anything to change, then nothing should change...
- name: 'Minimal call to IDP (no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -371,7 +371,7 @@
# Update (mass-update)
- name: 'Update all updatable IDP parameters - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -406,7 +406,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Update all updatable IDP parameters (no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider'
@@ -444,7 +444,7 @@
# Create complex IDP
- name: 'Create complex IDP - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider2'
@@ -481,7 +481,7 @@
_idp: '{{ idp.identity_provider }}'
- name: 'Create complex IDP (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: present
name: 'ansible_identity_provider2'
@@ -558,7 +558,7 @@
- False in (idps.identity_providers | map(attribute='is_enabled'))
- name: 'Delete identity_provider - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: absent
name: 'ansible_identity_provider'
@@ -579,7 +579,7 @@
- idp is changed
- name: 'Delete identity_provider (retry - no change) - CHECK_MODE'
check_mode: yes
check_mode: true
openstack.cloud.federation_idp:
state: absent
name: 'ansible_identity_provider'

View File

@@ -11,7 +11,7 @@
- name: Check output of creating network
assert:
that:
- infonet.network
- infonet.network is defined
- item in infonet.network
loop: "{{ expected_fields }}"
@@ -23,7 +23,7 @@
- name: Check output of network info
# TODO: Remove ignore_errors once SDK's search_networks() (re)implemented searching by id
ignore_errors: yes
ignore_errors: true
assert:
that:
- result.networks|length == 1

View File

@@ -0,0 +1,17 @@
---
expected_fields:
- description
- id
- name
- network_id
- network_type
- physical_network
- segmentation_id
network_name: segment_network
segment_name: example_segment
network_type: vlan
segmentation_id: 999
physical_network: public
initial_description: "example segment description"
updated_description: "updated segment description"

View File

@@ -0,0 +1,72 @@
---
- name: Create network {{ network_name }}
openstack.cloud.network:
cloud: "{{ cloud }}"
name: "{{ network_name }}"
state: present
- name: Create segment {{ segment_name }}
openstack.cloud.network_segment:
cloud: "{{ cloud }}"
name: "{{ segment_name }}"
description: "{{ initial_description }}"
network: "{{ network_name }}"
network_type: "{{ network_type }}"
segmentation_id: "{{ segmentation_id }}"
physical_network: "{{ physical_network }}"
state: present
register: segment
- name: Assert changed
assert:
that: segment is changed
- name: Assert segment fields
assert:
that: item in segment.network_segment
loop: "{{ expected_fields }}"
- name: Update segment {{ segment_name }} by name - no changes
openstack.cloud.network_segment:
cloud: "{{ cloud }}"
name: "{{ segment_name }}"
description: "{{ initial_description }}"
state: present
register: segment
- name: Assert not changed
assert:
that: segment is not changed
- name: Update segment {{ segment_name }} by all fields - changes
openstack.cloud.network_segment:
cloud: "{{ cloud }}"
name: "{{ segment_name }}"
description: "{{ updated_description }}"
network: "{{ network_name }}"
network_type: "{{ network_type }}"
segmentation_id: "{{ segmentation_id }}"
physical_network: "{{ physical_network }}"
state: present
register: segment
- name: Assert changed
assert:
that: segment is changed
- name: Delete segment {{ segment_name }}
openstack.cloud.network_segment:
cloud: "{{ cloud }}"
name: "{{ segment_name }}"
state: absent
register: segment
- name: Assert changed
assert:
that: segment is changed
- name: Delete network {{ network_name }}
openstack.cloud.network:
cloud: "{{ cloud }}"
name: "{{ network_name }}"
state: absent

View File

@@ -7,3 +7,4 @@ expected_fields:
- project_id
- target_project_id
- tenant_id
all_project_symbol: '*'

View File

@@ -69,6 +69,29 @@
id: "{{ rbac_policy.rbac_policy.id }}"
state: absent
- name: Create a new network RBAC policy by targeting all projects
openstack.cloud.neutron_rbac_policy:
cloud: "{{ cloud }}"
object_id: "{{ network.network.id }}"
object_type: 'network'
action: 'access_as_shared'
target_all_project: true
project_id: "{{ source_project.project.id }}"
register: rbac_policy
- name: Assert return values of neutron_rbac_policy module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(rbac_policy.rbac_policy.keys())|length == 0
- rbac_policy.rbac_policy.target_project_id == all_project_symbol
- name: Delete RBAC policy
openstack.cloud.neutron_rbac_policy:
cloud: "{{ cloud }}"
id: "{{ rbac_policy.rbac_policy.id }}"
state: absent
- name: Get all rbac policies for {{ source_project.project.name }} - after deletion
openstack.cloud.neutron_rbac_policies_info:
cloud: "{{ cloud }}"

View File

@@ -5,7 +5,7 @@
state: present
name: ansible_container
- name: Create object
- name: Create object from data
openstack.cloud.object:
cloud: "{{ cloud }}"
state: present
@@ -28,6 +28,47 @@
name: ansible_object
container: ansible_container
- name: Create object from file
block:
- name: Create temporary data file
ansible.builtin.tempfile:
register: tmp_file
- name: Populate data file
ansible.builtin.copy:
content: "this is a test"
dest: "{{ tmp_file.path }}"
- name: Create object from data file
openstack.cloud.object:
cloud: "{{ cloud }}"
state: present
name: ansible_object
filename: "{{ tmp_file.path }}"
container: ansible_container
register: object
always:
- name: Remove temporary data file
ansible.builtin.file:
path: "{{ tmp_file.path }}"
state: absent
when: tmp_file is defined and 'path' in tmp_file
- name: Assert return values of object module
assert:
that:
- object.object.id == "ansible_object"
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(object.object.keys())|length == 0
- name: Delete object
openstack.cloud.object:
cloud: "{{ cloud }}"
state: absent
name: ansible_object
container: ansible_container
- name: Delete container
openstack.cloud.object_container:
cloud: "{{ cloud }}"

View File

@@ -31,6 +31,21 @@
- ('cache-control' in container.container.metadata.keys()|map('lower'))
- container.container.metadata['foo'] == 'bar'
- name: Update container metadata
openstack.cloud.object_container:
cloud: "{{ cloud }}"
name: ansible_container
metadata:
'foo': 'baz'
register: container
- name: Verify container metadata was updated
assert:
that:
- container is changed
- ('cache-control' in container.container.metadata.keys()|map('lower'))
- container.container.metadata['foo'] == 'baz'
- name: Update a container
openstack.cloud.object_container:
cloud: "{{ cloud }}"
@@ -45,7 +60,7 @@
that:
- container is changed
- ('cache-control' not in container.container.metadata.keys()|map('lower'))
- "container.container.metadata == {'foo': 'bar'}"
- "container.container.metadata == {'foo': 'baz'}"
- container.container.read_ACL is none or container.container.read_ACL == ""
- name: Delete container
@@ -90,4 +105,4 @@
cloud: "{{ cloud }}"
state: absent
name: ansible_container2
delete_with_all_objects: yes
delete_with_all_objects: true

View File

@@ -0,0 +1,37 @@
---
test_container_unprefixed_name: ansible-test-container
test_container_prefixed_prefix: ansible-prefixed-test-container
test_container_prefixed_num: 2
test_object_data: "Hello, world!"
expected_fields_single:
- bytes
- bytes_used
- content_type
- count
- history_location
- id
- if_none_match
- is_content_type_detected
- is_newest
- meta_temp_url_key
- meta_temp_url_key_2
- name
- object_count
- read_ACL
- storage_policy
- sync_key
- sync_to
- timestamp
- versions_location
- write_ACL
expected_fields_multiple:
- bytes
- bytes_used
- count
- id
- name
- object_count

View File

@@ -0,0 +1,124 @@
---
- name: Generate list of containers to create
ansible.builtin.set_fact:
all_test_containers: >-
{{
[test_container_unprefixed_name]
+ (
[test_container_prefixed_prefix + '-']
| product(range(test_container_prefixed_num) | map('string'))
| map('join', '')
)
}}
- name: Run checks
block:
- name: Create all containers
openstack.cloud.object_container:
cloud: "{{ cloud }}"
name: "{{ item }}"
read_ACL: ".r:*,.rlistings"
loop: "{{ all_test_containers }}"
- name: Create an object in all containers
openstack.cloud.object:
cloud: "{{ cloud }}"
container: "{{ item }}"
name: hello.txt
data: "{{ test_object_data }}"
loop: "{{ all_test_containers }}"
- name: Fetch single containers by name
openstack.cloud.object_containers_info:
cloud: "{{ cloud }}"
name: "{{ item }}"
register: single_containers
loop: "{{ all_test_containers }}"
- name: Check that all fields are returned for single containers
ansible.builtin.assert:
that:
- (item.containers | length) == 1
- item.containers[0].name == item.item
- item.containers[0].bytes == (test_object_data | length)
- item.containers[0].read_ACL == ".r:*,.rlistings"
# allow new fields to be introduced but prevent fields from being removed
- (expected_fields_single | difference(item.containers[0].keys()) | length) == 0
quiet: true
loop: "{{ single_containers.results }}"
loop_control:
label: "{{ item.item }}"
- name: Fetch multiple containers by prefix
openstack.cloud.object_containers_info:
cloud: "{{ cloud }}"
prefix: "{{ test_container_prefixed_prefix }}"
register: multiple_containers
- name: Check that the correct number of prefixed containers were returned
ansible.builtin.assert:
that:
- (multiple_containers.containers | length) == test_container_prefixed_num
fail_msg: >-
Incorrect number of containers found
(found {{ multiple_containers.containers | length }},
expected {{ test_container_prefixed_num }})
quiet: true
- name: Check that all prefixed containers exist
ansible.builtin.assert:
that:
- >-
(test_container_prefixed_prefix + '-' + (item | string))
in (multiple_containers.containers | map(attribute='name'))
fail_msg: "Container not found: {{ test_container_prefixed_prefix + '-' + (item | string) }}"
quiet: true
loop: "{{ range(test_container_prefixed_num) | list }}"
loop_control:
label: "{{ test_container_prefixed_prefix + '-' + (item | string) }}"
- name: Check that the expected fields are returned for all prefixed containers
ansible.builtin.assert:
that:
- item.name.startswith(test_container_prefixed_prefix)
# allow new fields to be introduced but prevent fields from being removed
- (expected_fields_multiple | difference(item.keys()) | length) == 0
quiet: true
loop: "{{ multiple_containers.containers | sort(attribute='name') }}"
loop_control:
label: "{{ item.name }}"
- name: Fetch all containers
openstack.cloud.object_containers_info:
cloud: "{{ cloud }}"
register: all_containers
- name: Check that all expected containers were returned
ansible.builtin.assert:
that:
- item in (all_containers.containers | map(attribute='name'))
fail_msg: "Container not found: {{ item }}"
quiet: true
loop: "{{ all_test_containers }}"
- name: Check that the expected fields are returned for all containers
ansible.builtin.assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- (expected_fields_multiple | difference(item.keys()) | length) == 0
quiet: true
loop: "{{ all_containers.containers | selectattr('name', 'in', all_test_containers) }}"
loop_control:
label: "{{ item.name }}"
always:
- name: Delete all containers
openstack.cloud.object_container:
cloud: "{{ cloud }}"
name: "{{ item }}"
state: absent
delete_with_all_objects: true
loop: "{{ all_test_containers }}"

View File

@@ -1,6 +1,3 @@
binding_profile:
"pci_slot": "0000:03:11.1"
"physical_network": "provider"
expected_fields:
- allowed_address_pairs
- binding_host_id

View File

@@ -169,7 +169,7 @@
- ip_address: 10.5.5.69
name: "{{ port_name }}"
network: "{{ network_name }}"
no_security_groups: yes
no_security_groups: true
state: present
register: port
@@ -192,7 +192,7 @@
subnet_id: "{{ subnet.subnet.id }}"
name: "{{ port_name }}"
network: "{{ network_name }}"
no_security_groups: yes
no_security_groups: true
state: present
register: port_again
@@ -256,27 +256,6 @@
state: absent
name: ansible_security_group
- name: Create port (with binding profile)
openstack.cloud.port:
cloud: "{{ cloud }}"
state: present
name: "{{ port_name }}"
network: "{{ network_name }}"
binding_profile: "{{ binding_profile }}"
register: port
- name: Assert binding_profile exists in created port
assert:
that: "port.port['binding_profile']"
- debug: var=port
- name: Delete port (with binding profile)
openstack.cloud.port:
cloud: "{{ cloud }}"
state: absent
name: "{{ port_name }}"
- name: Delete subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"

View File

@@ -174,6 +174,38 @@
that:
- project.project.is_enabled == True
- name: Update project to add new extra_specs
openstack.cloud.project:
cloud: "{{ cloud }}"
state: present
name: ansible_project
extra_specs:
is_enabled: True
another_tag: True
register: project
- name: Assert return values of project module
assert:
that:
- project.project.is_enabled == True
- project.project.another_tag == True
- name: Update project to change existing extra_specs
openstack.cloud.project:
cloud: "{{ cloud }}"
state: present
name: ansible_project
extra_specs:
is_enabled: True
another_tag: False
register: project
- name: Assert return values of project module
assert:
that:
- project.project.is_enabled == True
- project.project.another_tag == False
- name: Delete project
openstack.cloud.project:
cloud: "{{ cloud }}"

View File

@@ -28,3 +28,9 @@ test_compute_quota:
ram: 5
server_group_members: 5
server_groups: 5
test_load_balancer_quota:
load_balancers: 5
health_monitors: 5
listeners: 5
pools: 5
members: 5

View File

@@ -0,0 +1,158 @@
---
- module_defaults:
group/openstack.cloud.openstack:
cloud: "{{ cloud }}"
name: "{{ test_project }}"
# Backward compatibility with Ansible 2.9
openstack.cloud.project:
cloud: "{{ cloud }}"
name: "{{ test_project }}"
openstack.cloud.quota:
cloud: "{{ cloud }}"
name: "{{ test_project }}"
block:
- name: Create test project
openstack.cloud.project:
state: present
- name: Clear quotas before tests
openstack.cloud.quota:
state: absent
register: default_quotas
- name: Set network quota
openstack.cloud.quota: "{{ test_network_quota }}"
register: quotas
- name: Assert changed
assert:
that: quotas is changed
- name: Assert field values
assert:
that: quotas.quotas.network[item.key] == item.value
loop: "{{ test_network_quota | dict2items }}"
- name: Set network quota again
openstack.cloud.quota: "{{ test_network_quota }}"
register: quotas
- name: Assert not changed
assert:
that: quotas is not changed
- name: Set volume quotas
openstack.cloud.quota: "{{ test_volume_quota }}"
register: quotas
- name: Assert changed
assert:
that: quotas is changed
- name: Assert field values
assert:
that: quotas.quotas.volume[item.key] == item.value
loop: "{{ test_volume_quota | dict2items }}"
- name: Set volume quotas again
openstack.cloud.quota: "{{ test_volume_quota }}"
register: quotas
- name: Assert not changed
assert:
that: quotas is not changed
- name: Set compute quotas
openstack.cloud.quota: "{{ test_compute_quota }}"
register: quotas
- name: Assert changed
assert:
that: quotas is changed
- name: Assert field values
assert:
that: quotas.quotas.compute[item.key] == item.value
loop: "{{ test_compute_quota | dict2items }}"
- name: Set compute quotas again
openstack.cloud.quota: "{{ test_compute_quota }}"
register: quotas
- name: Set load_balancer quotas
openstack.cloud.quota: "{{ test_load_balancer_quota }}"
register: quotas
- name: Assert changed
assert:
that: quotas is changed
- name: Assert field values
assert:
that: quotas.quotas.load_balancer[item.key] == item.value
loop: "{{ test_load_balancer_quota | dict2items }}"
- name: Set load_balancer quotas again
openstack.cloud.quota: "{{ test_load_balancer_quota }}"
register: quotas
- name: Assert not changed
assert:
that: quotas is not changed
- name: Unset all quotas
openstack.cloud.quota:
state: absent
register: quotas
- name: Assert defaults restore
assert:
that: quotas.quotas == default_quotas.quotas
- name: Set all quotas at once
openstack.cloud.quota:
"{{ [test_network_quota, test_volume_quota, test_compute_quota, test_load_balancer_quota] | combine }}"
register: quotas
- name: Assert changed
assert:
that: quotas is changed
- name: Assert volume values
assert:
that: quotas.quotas.volume[item.key] == item.value
loop: "{{ test_volume_quota | dict2items }}"
- name: Assert network values
assert:
that: quotas.quotas.network[item.key] == item.value
loop: "{{ test_network_quota | dict2items }}"
- name: Assert compute values
assert:
that: quotas.quotas.compute[item.key] == item.value
loop: "{{ test_compute_quota | dict2items }}"
- name: Assert load_balancer values
assert:
that: quotas.quotas.load_balancer[item.key] == item.value
loop: "{{ test_load_balancer_quota | dict2items }}"
- name: Set all quotas at once again
openstack.cloud.quota:
"{{ [test_network_quota, test_volume_quota, test_compute_quota, test_load_balancer_quota] | combine }}"
register: quotas
- name: Assert not changed
assert:
that: quotas is not changed
- name: Unset all quotas
openstack.cloud.quota:
state: absent
register: quotas
- name: Delete test project
openstack.cloud.project:
state: absent

View File

@@ -128,4 +128,9 @@
- name: Delete test project
openstack.cloud.project:
state: absent
state: absent
- import_tasks: loadbalancer.yml
tags:
- loadbalancer

View File

@@ -1,7 +1,7 @@
dns_zone_name: test.dns.zone.
recordset_name: testrecordset.test.dns.zone.
records: ['10.0.0.0']
updated_records: ['10.1.1.1']
records: ['10.0.0.0', '10.0.0.2']
updated_records: ['10.1.1.1', '10.0.0.2']
recordset_fields:
- action

View File

@@ -14,6 +14,15 @@
email: test@example.net
register: dns_zone
- name: Ensure recordset not present
openstack.cloud.recordset:
cloud: "{{ cloud }}"
zone: "{{ dns_zone.zone.name }}"
name: "{{ recordset_name }}"
recordset_type: "a"
records: "{{ records }}"
state: absent
- name: Create a recordset
openstack.cloud.recordset:
cloud: "{{ cloud }}"
@@ -22,14 +31,16 @@
recordset_type: "a"
records: "{{ records }}"
register: recordset
until: '"PENDING" not in recordset["recordset"].status'
retries: 10
delay: 5
- name: Verify recordset info
assert:
that:
- recordset is changed
- recordset["recordset"].name == recordset_name
- recordset["recordset"].zone_name == dns_zone.zone.name
- recordset["recordset"].records == records
- recordset["recordset"].records | list | sort == records | list | sort
- name: Assert recordset fields
assert:
@@ -71,7 +82,7 @@
- recordset is changed
- recordset["recordset"].zone_name == dns_zone.zone.name
- recordset["recordset"].name == recordset_name
- recordset["recordset"].records == updated_records
- recordset["recordset"].records | list | sort == updated_records | list | sort
- name: Assert recordset fields
assert:

View File

@@ -45,12 +45,6 @@
state: absent
user: admin
- name: Delete project
openstack.cloud.project:
cloud: "{{ cloud }}"
state: absent
name: ansible_project
- name: Create domain
openstack.cloud.identity_domain:
cloud: "{{ cloud }}"
@@ -78,6 +72,7 @@
state: present
name: ansible_user
domain: default
register: specific_user
- name: Create user in specific domain
openstack.cloud.identity_user:
@@ -138,6 +133,45 @@
that:
- role_assignment is changed
- name: Assign role to user in specific domain on default domain project
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: anotherrole
user: "{{ specific_user.user.id }}"
domain: default
project: ansible_project
register: role_assignment
- name: Assert role assignment
assert:
that:
- role_assignment is changed
- name: Revoke role to user in specific domain
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: anotherrole
user: "{{ specific_user.user.id }}"
domain: default
project: ansible_project
state: absent
register: role_assignment
- name: Assert role assignment revoked
assert:
that:
- role_assignment is changed
- name: Assign role to user in specific domain on default domain project
openstack.cloud.role_assignment:
cloud: "{{ cloud }}"
role: anotherrole
user: ansible_user
user_domain: "{{ specific_user.user.domain_id }}"
project: ansible_project
project_domain: default
register: role_assignment
- name: Delete group in default domain
openstack.cloud.identity_group:
cloud: "{{ cloud }}"
@@ -171,3 +205,10 @@
cloud: "{{ cloud }}"
state: absent
name: ansible_domain
- name: Delete project
openstack.cloud.project:
cloud: "{{ cloud }}"
state: absent
name: ansible_project

View File

@@ -384,7 +384,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.100
- name: Gather routers info
@@ -412,7 +412,7 @@
external_gateway_info:
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.100
- subnet: shade_subnet5
ip: 10.6.6.101
@@ -426,7 +426,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.100
- subnet: shade_subnet5
ip: 10.6.6.101
@@ -461,7 +461,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
- name: Update router (remove external fixed ips) again
@@ -473,7 +473,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
register: router
@@ -506,7 +506,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
- name: Gather routers info
@@ -533,7 +533,7 @@
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet: shade_subnet5
- subnet_id: shade_subnet5
ip: 10.6.6.101
register: router
@@ -558,6 +558,46 @@
assert:
that: router is not changed
- name: Create router without explicit IP address
openstack.cloud.router:
cloud: "{{ cloud }}"
state: present
name: "{{ router_name }}"
enable_snat: false
interfaces:
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet_id: shade_subnet5
register: router
- name: Assert idempotent module
assert:
that: router is changed
- name: Update router without explicit IP address
openstack.cloud.router:
cloud: "{{ cloud }}"
state: present
name: "{{ router_name }}"
enable_snat: false
interfaces:
- shade_subnet1
network: "{{ external_network_name }}"
external_fixed_ips:
- subnet_id: shade_subnet5
register: router
- name: Assert idempotent module
assert:
that: router is not changed
- name: Delete router
openstack.cloud.router:
cloud: "{{ cloud }}"
state: absent
name: "{{ router_name }}"
- name: Create router with simple interface
openstack.cloud.router:
cloud: "{{ cloud }}"
@@ -720,3 +760,5 @@
name: "{{ external_network_name }}"
- include_tasks: shared_network.yml
- include_tasks: shared_ext_network.yml

View File

@@ -0,0 +1,99 @@
---
# Test the case where we have a shared external network in one project used as
# the gateway on a router in a second project.
# See https://bugs.launchpad.net/ansible-collections-openstack/+bug/2049658
- name: Create the first project
openstack.cloud.project:
cloud: "{{ cloud }}"
state: present
name: "shared_ext_net_test_1"
description: "Project that contains the external network to be shared"
domain: default
is_enabled: True
register: project_1
- name: Create the external network to be shared
openstack.cloud.network:
cloud: "{{ cloud }}"
state: present
name: "{{ external_network_name }}"
project: "shared_ext_net_test_1"
external: true
shared: true
register: shared_ext_network
- name: Create subnet on external network
openstack.cloud.subnet:
cloud: "{{ cloud }}"
state: present
network_name: "{{ shared_ext_network.id }}"
name: "shared_ext_subnet"
project: "shared_ext_net_test_1"
cidr: "10.6.6.0/24"
register: shared_subnet
- name: Create the second project
openstack.cloud.project:
cloud: "{{ cloud }}"
state: present
name: "shared_ext_net_test_2"
description: "Project that contains the subnet to be shared"
domain: default
is_enabled: True
register: project_2
- name: Create router with gateway on shared external network
openstack.cloud.router:
cloud: "{{ cloud }}"
state: present
name: "shared_ext_net_test2_router"
project: "shared_ext_net_test_2"
network: "{{ external_network_name }}"
register: router
- name: Gather routers info
openstack.cloud.routers_info:
cloud: "{{ cloud }}"
name: "shared_ext_net_test2_router"
register: routers
- name: Verify routers info
assert:
that:
- routers.routers.0.id == router.router.id
- routers.routers.0.external_gateway_info.external_fixed_ips|length == 1
- name: Delete router
openstack.cloud.router:
cloud: "{{ cloud }}"
state: absent
name: "shared_ext_net_test2_router"
project: "shared_ext_net_test_2"
- name: Delete subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
state: absent
network_name: "{{ shared_ext_network.id }}"
name: "shared_ext_subnet"
project: "shared_ext_net_test_1"
- name: Delete network
openstack.cloud.network:
cloud: "{{ cloud }}"
state: absent
name: "{{ external_network_name }}"
project: "shared_ext_net_test_1"
- name: Delete project 2
openstack.cloud.project:
cloud: "{{ cloud }}"
state: absent
name: "shared_ext_net_test_2"
- name: Delete project 1
openstack.cloud.project:
cloud: "{{ cloud }}"
state: absent
name: "shared_ext_net_test_1"

View File

@@ -72,4 +72,25 @@
name: ansible_security_group
state: absent
- name: Create stateless security group
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group_stateless
stateful: false
state: present
description: 'Created from Ansible playbook'
register: security_group_stateless
- name: Assert return values of security_group module
assert:
that:
- security_group_stateless.security_group.name == 'ansible_security_group_stateless'
- security_group_stateless.security_group.stateful == False
- name: Delete stateless security group
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group_stateless
state: absent
- include_tasks: rules.yml

View File

@@ -32,7 +32,9 @@
- name: Assert return values of security_group_rule_info module
assert:
that:
- security_group_rules.security_group_rules | length == 0
- security_group_rules.security_group_rules | length in [1, 2]
- security_group_rules.security_group_rules | map(attribute='ether_type') | list | sort in
[['IPv4'], ['IPv6'], ['IPv4', 'IPv6']]
- name: Delete security group
openstack.cloud.security_group:
@@ -58,6 +60,47 @@
that:
- security_group is not changed
- name: Create security group without security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
security_group_rules: []
register: security_group
- name: Assert return values of security_group module
assert:
that:
- security_group is changed
- name: Create security group without security group rules again
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
security_group_rules: []
register: security_group
- name: Assert return values of security_group module
assert:
that:
- security_group is not changed
- name: Fetch security group rules
openstack.cloud.security_group_rule_info:
cloud: "{{ cloud }}"
security_group: ansible_security_group
register: security_group_rules
- name: Assert return values of security_group_rule_info module
assert:
that:
- security_group_rules.security_group_rules | length == 0
- name: Delete security group without security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
state: absent
- name: Create security group including security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
@@ -263,10 +306,11 @@
name: ansible_security_group
state: absent
- name: Create security group
- name: Create security group without security group rules
openstack.cloud.security_group:
cloud: "{{ cloud }}"
name: ansible_security_group
security_group_rules: []
state: present
register: security_group

View File

@@ -224,7 +224,7 @@
register: info
# TODO: Drop ignore_errors once openstacksdk's bug #2010135 has been solved.
# Ref.: https://storyboard.openstack.org/#!/story/2010135
ignore_errors: yes
ignore_errors: true
- name: Check info about server image name
assert:
@@ -232,7 +232,7 @@
- info.servers[0].image.name == image_name
# TODO: Drop ignore_errors once openstacksdk's bug #2010135 has been solved.
# Ref.: https://storyboard.openstack.org/#!/story/2010135
ignore_errors: yes
ignore_errors: true
- name: Delete server (FIP from pool/network)
openstack.cloud.server:
@@ -375,7 +375,7 @@
state: present
name: "{{ server_port }}"
network: "{{ server_network }}"
no_security_groups: yes
no_security_groups: true
fixed_ips:
- ip_address: 192.168.0.42
register: port
@@ -399,6 +399,9 @@
- port-id: "{{ port.port.id }}"
reuse_ips: false
state: present
tags:
- first
- second
wait: true
register: server
@@ -413,6 +416,7 @@
|selectattr('OS-EXT-IPS:type', 'equalto', 'floating')
|map(attribute='addr')
|list|length == 0
- server.server.tags == ["first", "second"]
- name: Find all floating ips for debugging
openstack.cloud.floating_ip_info:
@@ -454,6 +458,8 @@
- '{{ server_security_group }}'
- '{{ server_alt_security_group }}'
state: present
tags:
- yellow
wait: true
register: server_updated
@@ -475,6 +481,7 @@
- server_updated.server.addresses[server_network]|length == 2
- port.port.fixed_ips[0].ip_address in
server_updated.server.addresses[server_network]|map(attribute='addr')
- server_updated.server.tags == ['yellow']
# TODO: Verify networks once openstacksdk's issue #2010352 has been solved
# Ref.: https://storyboard.openstack.org/#!/story/2010352
#- server_updated.server.addresses.public|length > 0
@@ -509,6 +516,8 @@
- '{{ server_security_group }}'
- '{{ server_alt_security_group }}'
state: present
tags:
- yellow
wait: true
register: server_updated_again
@@ -517,6 +526,7 @@
that:
- server.server.id == server_updated_again.server.id
- server_updated_again is not changed
- server_updated_again.server.tags == ['yellow']
# TODO: Drop failure test once openstacksdk's issue #2010352 has been solved
# Ref.: https://storyboard.openstack.org/#!/story/2010352
@@ -555,7 +565,7 @@
- name: Delete updated server
openstack.cloud.server:
cloud: "{{ cloud }}"
delete_ips: yes
delete_ips: true
name: "{{ server_name }}"
state: absent
wait: true

View File

@@ -460,19 +460,14 @@
register: server
ignore_errors: true
- name: Assert shelve offload server
assert:
that:
- ((server is success)
or (server is not success
and "Cannot 'shelveOffload' instance" in server.msg
and "while it is in vm_state shelved_offloaded" in server.msg))
- name: Get info about server
openstack.cloud.server_info:
cloud: "{{ cloud }}"
server: ansible_server
register: servers
until: servers.servers.0.task_state == none
retries: 30
delay: 10
- name: Ensure status for server is SHELVED_OFFLOADED
# no change if server has been offloaded automatically after first shelve command
@@ -558,7 +553,7 @@
assert:
that:
- servers.servers.0.status == 'ACTIVE'
- server is not changed
- server is changed
- name: Reboot server (HARD)
openstack.cloud.server_action:
@@ -578,7 +573,7 @@
assert:
that:
- servers.servers.0.status == 'ACTIVE'
- server is not changed
- server is changed
- name: Delete server
openstack.cloud.server:

View File

@@ -1,9 +1,21 @@
---
- name: List all images
openstack.cloud.image_info:
cloud: "{{ cloud }}"
register: images
- name: Identify CirrOS image name
set_fact:
image_name: "{{ images.images|community.general.json_query(query)|first }}"
vars:
query: "[?starts_with(name, 'cirros')].name"
- name: Create server
openstack.cloud.server:
cloud: "{{ cloud }}"
state: present
name: "{{ server_name }}"
image: "cirros-0.5.2-x86_64-disk"
image: "{{ image_name }}"
flavor: "{{ flavor_name }}"
network: "{{ server_network }}"
auto_ip: false

View File

@@ -0,0 +1,5 @@
---
share_backend_name: GENERIC_BACKEND
share_type_name: test_share_type
share_type_description: Test share type for CI
share_type_alt_description: Changed test share type

View File

@@ -0,0 +1,130 @@
---
- name: Create share type
openstack.cloud.share_type:
name: "{{ share_type_name }}"
cloud: "{{ cloud }}"
state: present
extra_specs:
share_backend_name: "{{ share_backend_name }}"
snapshot_support: true
create_share_from_snapshot_support: true
description: "{{ share_type_description }}"
register: the_result
- name: Check created share type
vars:
the_share_type: "{{ the_result.share_type }}"
ansible.builtin.assert:
that:
- "'id' in the_result.share_type"
- the_share_type.description == share_type_description
- the_share_type.is_public == True
- the_share_type.name == share_type_name
- the_share_type.extra_specs['share_backend_name'] == share_backend_name
- the_share_type.extra_specs['snapshot_support'] == "True"
- the_share_type.extra_specs['create_share_from_snapshot_support'] == "True"
success_msg: >-
Created share type: {{ the_result.share_type.id }},
Name: {{ the_result.share_type.name }},
Description: {{ the_result.share_type.description }}
- name: Test share type info module
openstack.cloud.share_type_info:
name: "{{ share_type_name }}"
cloud: "{{ cloud }}"
register: info_result
- name: Check share type info result
ansible.builtin.assert:
that:
- info_result.share_type.id == the_result.share_type.id
- info_result.share_type.name == share_type_name
- info_result.share_type.description == share_type_description
success_msg: "Share type info retrieved successfully"
- name: Test, check idempotency
openstack.cloud.share_type:
name: "{{ share_type_name }}"
cloud: "{{ cloud }}"
state: present
extra_specs:
share_backend_name: "{{ share_backend_name }}"
snapshot_support: true
create_share_from_snapshot_support: true
description: "{{ share_type_description }}"
is_public: true
register: the_result
- name: Check result.changed is false
ansible.builtin.assert:
that:
- the_result.changed == false
success_msg: "Request with the same details lead to no changes"
- name: Add extra spec
openstack.cloud.share_type:
cloud: "{{ cloud }}"
name: "{{ share_type_name }}"
state: present
extra_specs:
share_backend_name: "{{ share_backend_name }}"
snapshot_support: true
create_share_from_snapshot_support: true
some_spec: fake_spec
description: "{{ share_type_alt_description }}"
is_public: true
register: the_result
- name: Check share type extra spec
ansible.builtin.assert:
that:
- "'some_spec' in the_result.share_type.extra_specs"
- the_result.share_type.extra_specs["some_spec"] == "fake_spec"
- the_result.share_type.description == share_type_alt_description
success_msg: >-
New extra specs: {{ the_result.share_type.extra_specs }}
- name: Remove extra spec by updating with reduced set
openstack.cloud.share_type:
cloud: "{{ cloud }}"
name: "{{ share_type_name }}"
state: present
extra_specs:
share_backend_name: "{{ share_backend_name }}"
snapshot_support: true
create_share_from_snapshot_support: true
description: "{{ share_type_alt_description }}"
is_public: true
register: the_result
- name: Check extra spec was removed
ansible.builtin.assert:
that:
- "'some_spec' not in the_result.share_type.extra_specs"
success_msg: "Extra spec was successfully removed"
- name: Delete share type
openstack.cloud.share_type:
cloud: "{{ cloud }}"
name: "{{ share_type_name }}"
state: absent
register: the_result
- name: Check deletion was successful
ansible.builtin.assert:
that:
- the_result.changed == true
success_msg: "Share type deleted successfully"
- name: Test deletion idempotency
openstack.cloud.share_type:
cloud: "{{ cloud }}"
name: "{{ share_type_name }}"
state: absent
register: the_result
- name: Check deletion idempotency
ansible.builtin.assert:
that:
- the_result.changed == false
success_msg: "Deletion idempotency works correctly"

View File

@@ -25,3 +25,4 @@ expected_fields:
- updated_at
- use_default_subnet_pool
subnet_name: shade_subnet
segment_name: example_segment

View File

@@ -17,10 +17,20 @@
name: "{{ network_name }}"
state: present
- name: Create network segment {{ segment_name }}
openstack.cloud.network_segment:
cloud: "{{ cloud }}"
name: "{{ segment_name }}"
network: "{{ network_name }}"
network_type: "vxlan"
segmentation_id: 1000
state: present
- name: Create subnet {{ subnet_name }} on network {{ network_name }}
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
network_segment: "{{ segment_name }}"
name: "{{ subnet_name }}"
state: present
enable_dhcp: "{{ enable_subnet_dhcp }}"
@@ -142,6 +152,48 @@
assert:
that: subnet is not changed
- name: Create subnet {{ subnet_name }} on network {{ network_name }} without gateway IP
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.0.0/24
disable_gateway_ip: true
register: subnet
- name: Assert changed
assert:
that: subnet is changed
- name: Create subnet {{ subnet_name }} on network {{ network_name }} without gateway IP
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.0.0/24
disable_gateway_ip: true
register: subnet
- name: Assert not changed
assert:
that: subnet is not changed
- name: Delete subnet {{ subnet_name }} again
openstack.cloud.subnet:
cloud: "{{ cloud }}"
name: "{{ subnet_name }}"
state: absent
register: subnet
- name: Delete network segment {{ segment_name }}
openstack.cloud.network_segment:
cloud: "{{ cloud }}"
name: "{{ segment_name }}"
network: "{{ network_name }}"
state: absent
- name: Delete network {{ network_name }}
openstack.cloud.network:
cloud: "{{ cloud }}"
@@ -150,3 +202,6 @@
- name: Subnet Allocation
include_tasks: subnet-allocation.yml
- name: Subnet Allocations from Subnet Pool
include_tasks: subnet-pool.yaml

View File

@@ -62,6 +62,81 @@
- subnet_result.subnets[0].allocation_pools.0.start == '192.168.0.2'
- subnet_result.subnets[0].allocation_pools.0.end == '192.168.0.8'
- name: Delete subnet {{ subnet_name }}
openstack.cloud.subnet:
cloud: "{{ cloud }}"
name: "{{ subnet_name }}"
state: absent
- name: Create subnet {{ subnet_name }} with multiple allocation pools on network {{ network_name }}
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_pools:
- start: 192.168.0.2
end: 192.168.0.4
- start: 192.168.0.10
end: 192.168.0.12
- 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_pools:
- start: 192.168.0.2
end: 192.168.0.4
- start: 192.168.0.10
end: 192.168.0.12
register: idem2
- name: Update subnet {{ subnet_name }} allocation pools
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.0.0/24
gateway_ip: 192.168.0.1
allocation_pools:
- start: 192.168.0.2
end: 192.168.0.8
- start: 192.168.0.10
end: 192.168.0.16
- name: Get Subnet Info
openstack.cloud.subnets_info:
cloud: "{{ cloud }}"
name: "{{ subnet_name }}"
register: subnet_result
# TODO(sshnaidm): Uncomment this section when the issue with allocation_pools is fixed
# - name: Verify Subnet Allocation Pools Exist
# assert:
# that:
# - idem2 is not changed
# - subnet_result.subnets is defined
# - subnet_result.subnets | length == 1
# - subnet_result.subnets[0].allocation_pools is defined
# - subnet_result.subnets[0].allocation_pools | length == 2
# - name: Verify Subnet Allocation Pools
# assert:
# that:
# - (subnet_result.subnets[0].allocation_pools.0.start == '192.168.0.2' and subnet_result.subnets[0].allocation_pools.0.end == '192.168.0.8') or
# (subnet_result.subnets[0].allocation_pools.0.start == '192.168.0.10' and subnet_result.subnets[0].allocation_pools.0.end == '192.168.0.16')
# - (subnet_result.subnets[0].allocation_pools.1.start == '192.168.0.2' and subnet_result.subnets[0].allocation_pools.1.end == '192.168.0.8') or
# (subnet_result.subnets[0].allocation_pools.1.start == '192.168.0.10' and subnet_result.subnets[0].allocation_pools.1.end == '192.168.0.16')
- name: Delete subnet {{ subnet_name }}
openstack.cloud.subnet:
cloud: "{{ cloud }}"

View File

@@ -0,0 +1,168 @@
---
# This test cover case when subnet is constructed
# with few prefixes and neutron API is required
# CIDR parameter to be used together with subnet pool.
- name: Create network {{ network_name }}
openstack.cloud.network:
cloud: "{{ cloud }}"
name: "{{ network_name }}"
state: present
- name: Create address_scope
openstack.cloud.address_scope:
cloud: "{{ cloud }}"
name: "{{ address_scope_name }}"
shared: false
ip_version: "4"
register: create_address_scope
- name: Create subnet pool
openstack.cloud.subnet_pool:
cloud: "{{ cloud }}"
name: "{{ subnet_pool_name }}"
is_shared: false
address_scope: "{{ address_scope_name }}"
prefixes:
- 192.168.0.0/24
- 192.168.42.0/24
register: subnet_pool
- name: Create subnet {{ subnet_name }} on network {{ network_name }} from subnet pool {{ subnet_pool_name }}
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
enable_dhcp: "{{ enable_subnet_dhcp }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.42.0/24 # we want specific cidr from subnet pool
ip_version: 4
subnet_pool: "{{ subnet_pool_name }}"
gateway_ip: 192.168.42.1
allocation_pool_start: 192.168.42.2
allocation_pool_end: 192.168.42.4
- name: Create subnet {{ subnet_name }} on network {{ network_name }} from subnet pool {{ subnet_pool_name }} again
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
enable_dhcp: "{{ enable_subnet_dhcp }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.42.0/24
ip_version: 4
subnet_pool: "{{ subnet_pool_name }}"
gateway_ip: 192.168.42.1
allocation_pool_start: 192.168.42.2
allocation_pool_end: 192.168.42.4
register: idem1
- name: Get Subnet Info
openstack.cloud.subnets_info:
cloud: "{{ cloud }}"
name: "{{ subnet_name }}"
register: subnet_result
- name: Verify Subnet Allocation Pools Exist
assert:
that:
- idem1 is not changed
- subnet_result.subnets is defined
- subnet_result.subnets | length == 1
- subnet_result.subnets[0].allocation_pools is defined
- subnet_result.subnets[0].allocation_pools | length == 1
- name: Verify Subnet Allocation Pools
assert:
that:
- subnet_result.subnets[0].allocation_pools.0.start == '192.168.42.2'
- subnet_result.subnets[0].allocation_pools.0.end == '192.168.42.4'
- name: Delete subnet {{ subnet_name }}
openstack.cloud.subnet:
cloud: "{{ cloud }}"
name: "{{ subnet_name }}"
state: absent
- name: Create subnet {{ subnet_name }} with multiple allocation pools on network {{ network_name }} from subnet pool {{ subnet_pool_name }}
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
enable_dhcp: "{{ enable_subnet_dhcp }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.42.0/24 # we want specific cidr from subnet pool
ip_version: 4
subnet_pool: "{{ subnet_pool_name }}"
gateway_ip: 192.168.42.1
allocation_pools:
- start: 192.168.42.2
end: 192.168.42.4
- start: 192.168.42.6
end: 192.168.42.8
- name: Create subnet {{ subnet_name }} on network {{ network_name }} from subnet pool {{ subnet_pool_name }} again
openstack.cloud.subnet:
cloud: "{{ cloud }}"
network_name: "{{ network_name }}"
enable_dhcp: "{{ enable_subnet_dhcp }}"
name: "{{ subnet_name }}"
state: present
cidr: 192.168.42.0/24
ip_version: 4
subnet_pool: "{{ subnet_pool_name }}"
gateway_ip: 192.168.42.1
allocation_pools:
- start: 192.168.42.2
end: 192.168.42.4
- start: 192.168.42.6
end: 192.168.42.8
register: idem2
- name: Get Subnet Info
openstack.cloud.subnets_info:
cloud: "{{ cloud }}"
name: "{{ subnet_name }}"
register: subnet_result
# NOT(gtema) Temporarily disable the check to land other gate fix
#- name: Verify Subnet Allocation Pools Exist
# assert:
# that:
# - idem2 is not changed
# - subnet_result.subnets is defined
# - subnet_result.subnets | length == 1
# - subnet_result.subnets[0].allocation_pools is defined
# - subnet_result.subnets[0].allocation_pools | length == 2
#
#- name: Verify Subnet Allocation Pools
# assert:
# that:
# - (subnet_result.subnets[0].allocation_pools.0.start == '192.168.42.2' and subnet_result.subnets[0].allocation_pools.0.end == '192.168.42.4') or
# (subnet_result.subnets[0].allocation_pools.0.start == '192.168.42.6' and subnet_result.subnets[0].allocation_pools.0.end == '192.168.42.8')
# - (subnet_result.subnets[0].allocation_pools.1.start == '192.168.42.2' and subnet_result.subnets[0].allocation_pools.1.end == '192.168.42.4') or
# (subnet_result.subnets[0].allocation_pools.1.start == '192.168.42.6' and subnet_result.subnets[0].allocation_pools.1.end == '192.168.42.8')
- name: Delete subnet {{ subnet_name }}
openstack.cloud.subnet:
cloud: "{{ cloud }}"
name: "{{ subnet_name }}"
state: absent
- name: Delete created subnet pool
openstack.cloud.subnet_pool:
cloud: "{{ cloud }}"
name: "{{ subnet_pool_name }}"
state: absent
- name: Delete created address scope
openstack.cloud.address_scope:
cloud: "{{ cloud }}"
name: "{{ address_scope_name }}"
state: absent
- name: Delete network {{ network_name }}
openstack.cloud.network:
cloud: "{{ cloud }}"
name: "{{ network_name }}"
state: absent

View File

@@ -0,0 +1 @@
trait_name: CUSTOM_ANSIBLE_TRAIT

View File

@@ -0,0 +1,28 @@
---
- name: Create trait
openstack.cloud.trait:
cloud: "{{ cloud }}"
state: present
id: "{{ trait_name }}"
until: result is success
retries: 5
delay: 20
register: result
- name: Assert trait
assert:
that:
- "'name' in result.trait"
- "result.trait.id == trait_name"
- name: Remove trait
openstack.cloud.trait:
cloud: "{{ cloud }}"
state: absent
id: "{{ trait_name }}"
register: result1
- name: Assert trait removed
assert:
that:
- "'trait' not in result1"

View File

@@ -0,0 +1,21 @@
expected_fields:
- created_at
- description
- id
- is_admin_state_up
- name
- port_id
- project_id
- revision_number
- status
- sub_ports
- tags
- tenant_id
- updated_at
trunk_name: ansible_trunk
parent_network_name: ansible_parent_port_network
parent_subnet_name: ansible_parent_port_subnet
parent_port_name: ansible_parent_port
subport_network_name: ansible_subport_network
subport_subnet_name: ansible_subport_subnet
subport_name: ansible_subport

View File

@@ -0,0 +1,131 @@
---
- name: Create parent network
openstack.cloud.network:
cloud: "{{ cloud }}"
state: present
name: "{{ parent_network_name }}"
external: true
register: parent_network
- name: Create parent subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
state: present
name: "{{ parent_subnet_name }}"
network_name: "{{ parent_network_name }}"
cidr: 10.5.5.0/24
register: parent_subnet
- name: Create parent port
openstack.cloud.port:
cloud: "{{ cloud }}"
state: present
name: "{{ parent_port_name }}"
network: "{{ parent_network_name }}"
fixed_ips:
- ip_address: 10.5.5.69
register: parent_port
- name: Create subport network
openstack.cloud.network:
cloud: "{{ cloud }}"
state: present
name: "{{ subport_network_name }}"
external: true
register: subport_network
- name: Create subport subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
state: present
name: "{{ subport_subnet_name }}"
network_name: "{{ subport_network_name }}"
cidr: 10.5.6.0/24
register: subport_subnet
- name: Create subport
openstack.cloud.port:
cloud: "{{ cloud }}"
state: present
name: "{{ subport_name }}"
network: "{{ subport_network_name }}"
fixed_ips:
- ip_address: 10.5.6.55
register: subport
- name: Create trunk
openstack.cloud.trunk:
cloud: "{{ cloud }}"
state: present
name: "{{ trunk_name }}"
port: "{{ parent_port_name }}"
register: trunk
- debug: var=trunk
- name: assert return values of trunk module
assert:
that:
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(trunk.trunk.keys())|length == 0
- name: Add subport to trunk
openstack.cloud.trunk:
cloud: "{{ cloud }}"
state: present
name: "{{ trunk_name }}"
port: "{{ parent_port_name }}"
sub_ports:
- port: "{{ subport_name }}"
segmentation_type: vlan
segmentation_id: 123
- name: Update subport from trunk
openstack.cloud.trunk:
cloud: "{{ cloud }}"
state: present
name: "{{ trunk_name }}"
port: "{{ parent_port_name }}"
sub_ports: []
- name: Delete trunk
openstack.cloud.trunk:
cloud: "{{ cloud }}"
state: absent
name: "{{ trunk_name }}"
- name: Delete subport
openstack.cloud.port:
cloud: "{{ cloud }}"
state: absent
name: "{{ subport_name }}"
- name: Delete subport subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
state: absent
name: "{{ subport_subnet_name }}"
- name: Delete subport network
openstack.cloud.network:
cloud: "{{ cloud }}"
state: absent
name: "{{ subport_network_name }}"
- name: Delete parent port
openstack.cloud.port:
cloud: "{{ cloud }}"
state: absent
name: "{{ parent_port_name }}"
- name: Delete parent subnet
openstack.cloud.subnet:
cloud: "{{ cloud }}"
state: absent
name: "{{ parent_subnet_name }}"
- name: Delete parent network
openstack.cloud.network:
cloud: "{{ cloud }}"
state: absent
name: "{{ parent_network_name }}"

View File

@@ -37,7 +37,7 @@
- name: Check info
assert:
that:
- info1.volumes | selectattr("id", "equalto", "{{ info.volumes.0.id }}") | list | length == 1
- info1.volumes | selectattr("id", "equalto", info.volumes.0.id) | list | length == 1
- info1.volumes.0.name == 'ansible_test'
- info1.volumes.0.status == None

View File

@@ -0,0 +1,32 @@
test_volume: ansible_test_volume
managed_volume: managed_test_volume
expected_fields:
- attachments
- availability_zone
- consistency_group_id
- created_at
- updated_at
- description
- extended_replication_status
- group_id
- host
- image_id
- is_bootable
- is_encrypted
- is_multiattach
- migration_id
- migration_status
- project_id
- replication_driver_data
- replication_status
- scheduler_hints
- size
- snapshot_id
- source_volume_id
- status
- user_id
- volume_image_metadata
- volume_type
- id
- name
- metadata

View File

@@ -0,0 +1,65 @@
---
- name: Create volume
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: present
size: 1
name: "{{ test_volume }}"
description: Test volume
register: vol
- assert:
that: item in vol.volume
loop: "{{ expected_fields }}"
- name: Unmanage volume
openstack.cloud.volume_manage:
cloud: "{{ cloud }}"
state: absent
name: "{{ vol.volume.id }}"
- name: Unmanage volume again
openstack.cloud.volume_manage:
cloud: "{{ cloud }}"
state: absent
name: "{{ vol.volume.id }}"
register: unmanage_idempotency
- assert:
that:
- unmanage_idempotency is not changed
- name: Manage volume
openstack.cloud.volume_manage:
cloud: "{{ cloud }}"
state: present
source_name: volume-{{ vol.volume.id }}
host: "{{ vol.volume.host }}"
name: "{{ managed_volume }}"
register: new_vol
- assert:
that:
- new_vol.volume.name == managed_volume
- name: Manage volume again
openstack.cloud.volume_manage:
cloud: "{{ cloud }}"
state: present
source_name: volume-{{ vol.volume.id }}
host: "{{ vol.volume.host }}"
name: "{{ managed_volume }}"
register: vol_idempotency
- assert:
that:
- vol_idempotency is not changed
- pause:
seconds: 10
- name: Delete volume
openstack.cloud.volume:
cloud: "{{ cloud }}"
state: absent
name: "{{ managed_volume }}"

View File

@@ -0,0 +1,9 @@
expected_fields:
- availability_zone
- binary
- disabled_reason
- host
- name
- state
- status
- updated_at

View File

@@ -0,0 +1,23 @@
---
- name: Fetch volume services
openstack.cloud.volume_service_info:
cloud: "{{ cloud }}"
register: volume_services
- name: Assert return values of volume_service_info module
assert:
that:
- volume_services.volume_services | length > 0
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(volume_services.volume_services[0].keys())|length == 0
- name: Fetch volume services with filters
openstack.cloud.volume_service_info:
cloud: "{{ cloud }}"
binary: "cinder-volume"
register: volume_services
- name: Assert return values of volume_service_info module
assert:
that:
- volume_services.volume_services | length > 0

View File

@@ -0,0 +1,10 @@
---
volume_backend_name: LVM_iSCSI
volume_type_name: test_type
volume_type_description: Test volume type
enc_provider_name: nova.volume.encryptors.luks.LuksEncryptor
enc_cipher: aes-xts-plain64
enc_control_location: front-end
enc_control_alt_location: back-end
enc_key_size: 256

View File

@@ -0,0 +1,85 @@
---
- name: Create volume type
openstack.cloud.volume_type:
name: "{{ volume_type_name }}"
cloud: "{{ cloud }}"
state: present
extra_specs:
volume_backend_name: "{{ volume_backend_name }}"
description: "{{ volume_type_description }}"
is_public: true
register: the_result
- name: Check created volume type
vars:
the_volume: "{{ the_result.volume_type }}"
ansible.builtin.assert:
that:
- "'id' in the_result.volume_type"
- the_volume.description == volume_type_description
- the_volume.is_public == True
- the_volume.name == volume_type_name
- the_volume.extra_specs['volume_backend_name'] == volume_backend_name
success_msg: >-
Created volume: {{ the_result.volume_type.id }},
Name: {{ the_result.volume_type.name }},
Description: {{ the_result.volume_type.description }}
- name: Test, check idempotency
openstack.cloud.volume_type:
name: "{{ volume_type_name }}"
cloud: "{{ cloud }}"
state: present
extra_specs:
volume_backend_name: "{{ volume_backend_name }}"
description: "{{ volume_type_description }}"
is_public: true
register: the_result
- name: Check result.changed is false
ansible.builtin.assert:
that:
- the_result.changed == false
success_msg: "Request with the same details lead to no changes"
- name: Add extra spec
openstack.cloud.volume_type:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
state: present
extra_specs:
volume_backend_name: "{{ volume_backend_name }}"
some_spec: fake_spec
description: "{{ volume_type_description }}"
is_public: true
register: the_result
- name: Check volume type extra spec
ansible.builtin.assert:
that:
- "'some_spec' in the_result.volume_type.extra_specs"
- the_result.volume_type.extra_specs["some_spec"] == "fake_spec"
success_msg: >-
New extra specs: {{ the_result.volume_type.extra_specs }}
# is_public update attempt using openstacksdk result in unexpected attribute
# error... TODO: Find solution
#
# - name: Make volume type private
# openstack.cloud.volume_type:
# cloud: "{{ cloud }}"
# name: "{{ volume_type_alt_name }}"
# state: present
# extra_specs:
# volume_backend_name: "{{ volume_backend_name }}"
# # some_other_spec: test
# description: Changed 3rd time test volume type
# is_public: true
# register: the_result
- name: Volume encryption tests
ansible.builtin.include_tasks: volume_encryption.yml
- name: Delete volume type
openstack.cloud.volume_type:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
state: absent
register: the_result

View File

@@ -0,0 +1,67 @@
---
- name: Test, Volume type has no encryption
openstack.cloud.volume_type_info:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
register: the_result
- name: Check volume type has no encryption
ansible.builtin.assert:
that:
- the_result.encryption.id == None
success_msg: >-
Success: Volume type has no encryption at the moment
- name: Test, create volume type encryption
openstack.cloud.volume_type_encryption:
cloud: "{{ cloud }}"
volume_type: "{{ volume_type_name }}"
state: present
encryption_provider: "{{ enc_provider_name }}"
encryption_cipher: "{{ enc_cipher }}"
encryption_control_location: "{{ enc_control_location }}"
encryption_key_size: "{{ enc_key_size }}"
register: the_result
- name: Check volume type encryption
ansible.builtin.assert:
that:
- the_result.encryption.cipher == enc_cipher
- the_result.encryption.control_location == enc_control_location
- the_result.encryption.key_size == enc_key_size
- the_result.encryption.provider == enc_provider_name
success_msg: >-
Success: {{ the_result.encryption.encryption_id }}
- name: Test, update volume type encryption
openstack.cloud.volume_type_encryption:
cloud: "{{ cloud }}"
volume_type: "{{ volume_type_name }}"
state: present
encryption_provider: "{{ enc_provider_name }}"
encryption_cipher: "{{ enc_cipher }}"
encryption_control_location: "{{ enc_control_alt_location }}"
encryption_key_size: "{{ enc_key_size }}"
register: the_result
- name: Check volume type encryption change
ansible.builtin.assert:
that:
- the_result.encryption.control_location == enc_control_alt_location
success_msg: >-
New location: {{ the_result.encryption.control_location }}
- name: Test, delete volume type encryption
openstack.cloud.volume_type_encryption:
cloud: "{{ cloud }}"
volume_type: "{{ volume_type_name }}"
state: absent
register: the_result
- name: Get volume type details
openstack.cloud.volume_type_info:
cloud: "{{ cloud }}"
name: "{{ volume_type_name }}"
register: the_result
- name: Check volume type has no encryption
ansible.builtin.assert:
that:
- the_result.encryption.id == None
success_msg: >-
Success: Volume type has no encryption

View File

@@ -75,10 +75,10 @@ ansible-galaxy collection install --requirements-file ci/requirements.yml
if [ -z "$PIP_INSTALL" ]; then
tox -ebuild
ansible-galaxy collection install "$(find build_artifact/ -maxdepth 1 -name 'openstack-cloud-*')" --force
TEST_COLLECTIONS_PATHS=${HOME}/.ansible/collections:$ANSIBLE_COLLECTIONS_PATHS
TEST_COLLECTIONS_PATHS=${HOME}/.ansible/collections:$ANSIBLE_COLLECTIONS_PATH
else
pip freeze | grep ansible-collections-openstack
TEST_COLLECTIONS_PATHS=$VIRTUAL_ENV/share/ansible/collections:$ANSIBLE_COLLECTIONS_PATHS
TEST_COLLECTIONS_PATHS=$VIRTUAL_ENV/share/ansible/collections:$ANSIBLE_COLLECTIONS_PATH
fi
# We need to source the current tox environment so that Ansible will
@@ -124,12 +124,17 @@ if [ ! -e /etc/magnum ]; then
tag_opt+=" --skip-tags coe_cluster,coe_cluster_template"
fi
if ! systemctl is-enabled devstack@m-api.service 2>&1; then
# Skip share_type tasks if Manila is not available
tag_opt+=" --skip-tags share_type"
fi
cd ci/
# Run tests
set -o pipefail
# shellcheck disable=SC2086
ANSIBLE_COLLECTIONS_PATHS=$TEST_COLLECTIONS_PATHS ansible-playbook \
ANSIBLE_COLLECTIONS_PATH=$TEST_COLLECTIONS_PATHS ansible-playbook \
-vvv ./run-collection.yml \
-e "sdk_version=${SDK_VER} cloud=${CLOUD} cloud_alt=${CLOUD_ALT} ${ANSIBLE_VARS}" \
${tag_opt} 2>&1 | sudo tee /opt/stack/logs/test_output.log

View File

@@ -5,6 +5,7 @@
roles:
- { role: address_scope, tags: address_scope }
- { role: application_credential, tags: application_credential }
- { role: auth, tags: auth }
- { role: catalog_service, tags: catalog_service }
- { role: coe_cluster, tags: coe_cluster }
@@ -31,10 +32,14 @@
- { role: loadbalancer, tags: loadbalancer }
- { role: logging, tags: logging }
- { role: network, tags: network }
- { role: network_segment, tags: network_segment }
- { role: neutron_rbac_policy, tags: neutron_rbac_policy }
- { role: object, tags: object }
- { role: object_container, tags: object_container }
- { role: object_containers_info, tags: object_containers_info }
- { role: port, tags: port }
- { role: trait, tags: trait }
- { role: trunk, tags: trunk }
- { role: project, tags: project }
- { role: quota, tags: quota }
- { role: recordset, tags: recordset }
@@ -49,10 +54,14 @@
- { role: server_group, tags: server_group }
- { role: server_metadata, tags: server_metadata }
- { role: server_volume, tags: server_volume }
- { role: share_type, tags: share_type }
- { role: stack, tags: stack }
- { role: subnet, tags: subnet }
- { role: subnet_pool, tags: subnet_pool }
- { role: volume, tags: volume }
- { role: volume_type, tags: volume_type }
- { role: volume_backup, tags: volume_backup }
- { role: volume_manage, tags: volume_manage }
- { role: volume_service, tags: volume_service }
- { role: volume_snapshot, tags: volume_snapshot }
- { role: volume_type_access, tags: volume_type_access }

View File

@@ -11,7 +11,7 @@ For hacking on the Ansible OpenStack collection it helps to [prepare a DevStack
## Hosting
* [Bug tracker][storyboard]
* [Bug tracker][bugtracker]
* [Mailing list `openstack-discuss@lists.openstack.org`][openstack-discuss].
Prefix subjects with `[aoc]` or `[aco]` for faster responses.
* [Code Hosting][opendev-a-c-o]
@@ -188,4 +188,4 @@ Read [Release Guide](releasing.md) on how to publish new releases.
[openstacksdk-cloud-layer-stays]: https://meetings.opendev.org/irclogs/%23openstack-sdks/%23openstack-sdks.2022-04-27.log.html
[openstacksdk-to-dict]: https://opendev.org/openstack/openstacksdk/src/branch/master/openstack/resource.py
[openstacksdk]: https://opendev.org/openstack/openstacksdk
[storyboard]: https://storyboard.openstack.org/#!/project/openstack/ansible-collections-openstack
[bugtracker]: https://bugs.launchpad.net/ansible-collections-openstack

View File

@@ -11,7 +11,7 @@ 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
issues: https://bugs.launchpad.net/ansible-collections-openstack
build_ignore:
- "*.tar.gz"
- build_artifact
@@ -32,4 +32,4 @@ build_ignore:
- .vscode
- ansible_collections_openstack.egg-info
- changelogs
version: 2.0.0
version: 2.5.0

View File

@@ -11,7 +11,7 @@ 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
issues: https://bugs.launchpad.net/ansible-collections-openstack
build_ignore:
- "*.tar.gz"
- build_artifact

View File

@@ -2,12 +2,15 @@ requires_ansible: ">=2.8"
action_groups:
openstack:
- address_scope
- application_credential
- auth
- baremetal_deploy_template
- baremetal_inspect
- baremetal_node
- baremetal_node_action
- baremetal_node_info
- baremetal_port
- baremetal_port_group
- baremetal_port_info
- catalog_service
- catalog_service_info
@@ -49,11 +52,13 @@ action_groups:
- lb_pool
- loadbalancer
- network
- network_segment
- networks_info
- neutron_rbac_policies_info
- neutron_rbac_policy
- object
- object_container
- object_containers_info
- port
- port_info
- project
@@ -75,15 +80,20 @@ action_groups:
- server_info
- server_metadata
- server_volume
- share_type
- share_type_info
- stack
- stack_info
- subnet
- subnet_pool
- subnets_info
- trunk
- volume
- volume_manage
- volume_backup
- volume_backup_info
- volume_info
- volume_service_info
- volume_snapshot
- volume_snapshot_info
- volume_type_access

View File

@@ -46,7 +46,7 @@ options:
description:
- Should ansible wait until the requested resource is complete.
type: bool
default: yes
default: true
timeout:
description:
- How long should ansible wait for the requested resource.
@@ -60,7 +60,7 @@ options:
validate_certs:
description:
- Whether or not SSL API requests should be verified.
- Before Ansible 2.3 this defaulted to C(yes).
- Before Ansible 2.3 this defaulted to C(true).
type: bool
aliases: [ verify ]
ca_cert:

View File

@@ -96,6 +96,18 @@ options:
only.
type: bool
default: false
only_ipv4:
description:
- Use only ipv4 addresses for ansible_host and ansible_ssh_host.
- Using I(only_ipv4) helps when running Ansible in a ipv4 only setup.
type: bool
default: false
server_filters:
description:
- A dictionary of server filter value pairs.
- Available parameters can be seen under https://docs.openstack.org/api-ref/compute/#list-servers
type: dict
default: {}
show_all:
description:
- Whether all servers should be listed or not.
@@ -271,9 +283,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if not attempt_to_read_cache or cache_needs_update:
self.display.vvvv('Retrieving servers from Openstack clouds')
clouds_yaml_path = self.get_option('clouds_yaml_path')
config_files = (
openstack.config.loader.CONFIG_FILES
+ ([clouds_yaml_path] if clouds_yaml_path else []))
config_files = openstack.config.loader.CONFIG_FILES
if clouds_yaml_path:
config_files = clouds_yaml_path + config_files
config = openstack.config.loader.OpenStackConfig(
config_files=config_files)
@@ -293,10 +305,6 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
clouds = [openstack.connection.Connection(config=cloud_region)
for cloud_region in cloud_regions]
if self.get_option('private'):
for cloud in self.clouds:
cloud.private = True
self.display.vvvv(
'Found {0} OpenStack cloud(s)'
.format(len(clouds)))
@@ -307,6 +315,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
expand_hostvars = self.get_option('expand_hostvars')
all_projects = self.get_option('all_projects')
server_filters = self.get_option('server_filters')
servers = []
def _expand_server(server, cloud, volumes):
@@ -353,7 +362,8 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
all_projects=all_projects,
# details are required because 'addresses'
# attribute must be populated
details=True)
details=True,
**server_filters)
]:
servers.append(server)
except openstack.exceptions.OpenStackCloudException as e:
@@ -388,12 +398,19 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if address['OS-EXT-IPS:type'] == 'floating'),
None)
fixed_ip = next(
(address['addr'] for address in addresses
if address['OS-EXT-IPS:type'] == 'fixed'),
None)
if self.get_option('only_ipv4'):
fixed_ip = next(
(address['addr'] for address in addresses
if (address['OS-EXT-IPS:type'] == 'fixed' and address['version'] == 4)),
None)
ip = floating_ip if floating_ip is not None else fixed_ip
else:
fixed_ip = next(
(address['addr'] for address in addresses
if address['OS-EXT-IPS:type'] == 'fixed'),
None)
ip = floating_ip if floating_ip is not None and not self.get_option('private') else fixed_ip
if ip is not None:
host_vars['ansible_ssh_host'] = ip

View File

@@ -183,7 +183,7 @@ def openstack_cloud_from_module(module, min_version=None, max_version=None):
" excluded.")
for param in (
'auth', 'region_name', 'validate_certs',
'ca_cert', 'client_key', 'api_timeout', 'auth_type'):
'ca_cert', 'client_cert', 'client_key', 'api_timeout', 'auth_type'):
if module.params[param] is not None:
module.fail_json(msg=fail_message.format(param=param))
# For 'interface' parameter, fail if we receive a non-default value
@@ -199,6 +199,7 @@ def openstack_cloud_from_module(module, min_version=None, max_version=None):
verify=module.params['validate_certs'],
cacert=module.params['ca_cert'],
key=module.params['client_key'],
cert=module.params['client_cert'],
api_timeout=module.params['api_timeout'],
interface=module.params['interface'],
)
@@ -358,7 +359,7 @@ class OpenStackModule:
" excluded.")
for param in (
'auth', 'region_name', 'validate_certs',
'ca_cert', 'client_key', 'api_timeout', 'auth_type'):
'ca_cert', 'client_cert', 'client_key', 'api_timeout', 'auth_type'):
if self.params[param] is not None:
self.fail_json(msg=fail_message.format(param=param))
# For 'interface' parameter, fail if we receive a non-default value
@@ -373,6 +374,7 @@ class OpenStackModule:
verify=self.params['validate_certs'],
cacert=self.params['ca_cert'],
key=self.params['client_key'],
cert=self.params['client_cert'],
api_timeout=self.params['api_timeout'],
interface=self.params['interface'],
)

View File

@@ -40,7 +40,7 @@ options:
description:
- Whether this address scope is shared or not.
type: bool
default: 'no'
default: 'false'
aliases: ['shared']
extra_specs:
description:

View File

@@ -0,0 +1,332 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 Red Hat, Inc.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r"""
---
module: application_credential
short_description: Manage OpenStack Identity (Keystone) application credentials
author: OpenStack Ansible SIG
description:
- Create or delete an OpenStack Identity (Keystone) application credential.
- When the secret parameter is not set a secret will be generated and returned
- in the response. Existing credentials cannot be modified so running this module
- against an existing credential will result in it being deleted and recreated.
- This needs to be taken into account when the secret is generated, as the secret
- will change on each run of the module.
options:
name:
description:
- Name of the application credential.
required: true
type: str
description:
description:
- Application credential description.
type: str
secret:
description:
- Secret to use for authentication
- (if not provided, one will be generated).
type: str
roles:
description:
- Roles to authorize (name or ID).
type: list
elements: dict
suboptions:
name:
description: Name of role
type: str
id:
description: ID of role
type: str
domain_id:
description: Domain ID
type: str
expires_at:
description:
- Sets an expiration date for the application credential,
- format of YYYY-mm-ddTHH:MM:SS
- (if not provided, the application credential will not expire).
type: str
unrestricted:
description:
- Enable application credential to create and delete other application
- credentials and trusts (this is potentially dangerous behavior and is
- disabled by default).
default: false
type: bool
access_rules:
description:
- List of access rules, each containing a request method, path, and service.
type: list
elements: dict
suboptions:
service:
description: Name of service endpoint
type: str
required: true
path:
description: Path portion of access URL
type: str
required: true
method:
description: HTTP method
type: str
required: true
state:
description:
- Should the resource be present or absent.
- Application credentials are immutable so running with an existing present
- credential will result in the credential being deleted and recreated.
choices: [present, absent]
default: present
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
"""
EXAMPLES = r"""
- name: Create application credential
openstack.cloud.application_credential:
cloud: mycloud
description: demodescription
name: democreds
state: present
- name: Create application credential with expiration, access rules and roles
openstack.cloud.application_credential:
cloud: mycloud
description: demodescription
name: democreds
access_rules:
- service: "compute"
path: "/v2.1/servers"
method: "GET"
expires_at: "2024-02-29T09:29:59"
roles:
- name: Member
state: present
- name: Delete application credential
openstack.cloud.application_credential:
cloud: mycloud
name: democreds
state: absent
"""
RETURN = r"""
application_credential:
description: Dictionary describing the project.
returned: On success when I(state) is C(present).
type: dict
contains:
id:
description: The ID of the application credential.
type: str
sample: "2e73d1b4f0cb473f920bd54dfce3c26d"
name:
description: The name of the application credential.
type: str
sample: "appcreds"
secret:
description: Secret to use for authentication
(if not provided, returns the generated value).
type: str
sample: "JxE7LajLY75NZgDH1hfu0N_6xS9hQ-Af40W3"
description:
description: A description of the application credential's purpose.
type: str
sample: "App credential"
expires_at:
description: The expiration time of the application credential in UTC,
if one was specified.
type: str
sample: "2024-02-29T09:29:59.000000"
project_id:
description: The ID of the project the application credential was created
for and that authentication requests using this application
credential will be scoped to.
type: str
sample: "4b633c451ac74233be3721a3635275e5"
roles:
description: A list of one or more roles that this application credential
has associated with its project. A token using this application
credential will have these same roles.
type: list
elements: dict
sample: [{"name": "Member"}]
access_rules:
description: A list of access_rules objects
type: list
elements: dict
sample:
- id: "edecb6c791d541a3b458199858470d20"
service: "compute"
path: "/v2.1/servers"
method: "GET"
unrestricted:
description: A flag indicating whether the application credential may be
used for creation or destruction of other application credentials
or trusts.
type: bool
cloud:
description: The current cloud config with the username and password replaced
with the name and secret of the application credential. This
can be passed to the cloud parameter of other tasks, or written
to an openstack cloud config file.
returned: On success when I(state) is C(present).
type: dict
sample:
auth_type: "v3applicationcredential"
auth:
auth_url: "https://192.0.2.1/identity"
application_credential_secret: "JxE7LajLY75NZgDH1hfu0N_6xS9hQ-Af40W3"
application_credential_id: "3e73d1b4f0cb473f920bd54dfce3c26d"
"""
import copy
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
OpenStackModule,
)
try:
import openstack.config
except ImportError:
pass
class IdentityApplicationCredentialModule(OpenStackModule):
argument_spec = dict(
name=dict(required=True),
description=dict(),
secret=dict(no_log=True),
roles=dict(
type="list",
elements="dict",
options=dict(name=dict(), id=dict(), domain_id=dict()),
),
expires_at=dict(),
unrestricted=dict(type="bool", default=False),
access_rules=dict(
type="list",
elements="dict",
options=dict(
service=dict(required=True),
path=dict(required=True),
method=dict(required=True),
),
),
state=dict(default="present", choices=["absent", "present"]),
)
module_kwargs = dict()
cloud = None
def openstack_cloud_from_module(self):
# Fetch cloud param before it is popped
self.cloud = self.params["cloud"]
return OpenStackModule.openstack_cloud_from_module(self)
def run(self):
state = self.params["state"]
creds = self._find()
if state == "present" and not creds:
# Create creds
creds = self._create().to_dict(computed=False)
cloud_config = self._get_cloud_config(creds)
self.exit_json(
changed=True, application_credential=creds, cloud=cloud_config
)
elif state == "present" and creds:
# Recreate immutable creds
self._delete(creds)
creds = self._create().to_dict(computed=False)
cloud_config = self._get_cloud_config(creds)
self.exit_json(
changed=True, application_credential=creds, cloud=cloud_config
)
elif state == "absent" and creds:
# Delete creds
self._delete(creds)
self.exit_json(changed=True)
elif state == "absent" and not creds:
# Do nothing
self.exit_json(changed=False)
def _get_user_id(self):
return self.conn.session.get_user_id()
def _create(self):
kwargs = dict(
(k, self.params[k])
for k in [
"name",
"description",
"secret",
"expires_at",
"unrestricted",
"access_rules",
]
if self.params[k] is not None
)
roles = self.params["roles"]
if roles:
kwroles = []
for role in roles:
kwroles.append(
dict(
(k, role[k])
for k in ["name", "id", "domain_id"]
if role[k] is not None
)
)
kwargs["roles"] = kwroles
kwargs["user"] = self._get_user_id()
creds = self.conn.identity.create_application_credential(**kwargs)
return creds
def _get_cloud_config(self, creds):
cloud_region = openstack.config.OpenStackConfig().get_one(self.cloud)
conf = cloud_region.config
cloud_config = copy.deepcopy(conf)
cloud_config["auth_type"] = "v3applicationcredential"
cloud_config["auth"] = {
"application_credential_id": creds["id"],
"application_credential_secret": creds["secret"],
"auth_url": conf["auth"]["auth_url"],
}
return cloud_config
def _delete(self, creds):
user = self._get_user_id()
self.conn.identity.delete_application_credential(user, creds.id)
def _find(self):
name = self.params["name"]
user = self._get_user_id()
return self.conn.identity.find_application_credential(
user=user, name_or_id=name
)
def main():
module = IdentityApplicationCredentialModule()
module()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,198 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 StackHPC Ltd.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
module: baremetal_deploy_template
short_description: Create/Delete Bare Metal deploy template Resources from OpenStack
author: OpenStack Ansible SIG
description:
- Create, Update and Remove ironic deploy templates from OpenStack.
options:
extra:
description:
- A set of one or more arbitrary metadata key and value pairs.
type: dict
id:
description:
- ID of the deploy template.
- Will be auto-generated if not specified.
type: str
aliases: ['uuid']
name:
description:
- Name of the deploy template.
- Must be formatted as a trait name (see API reference).
- Required when the deploy template is created, after which the
name or ID may be used.
type: str
steps:
description:
- List of deploy steps to apply.
- Required when the deploy template is created.
type: list
elements: dict
state:
description:
- Indicates desired state of the resource
choices: ['present', 'absent']
default: present
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = r'''
- name: Create Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: devstack
state: present
name: CUSTOM_FOO
steps:
- interface: bios
step: apply_configuration
args:
settings:
- name: LogicalProc
value: Enabled
priority: 110
extra:
something: extra
register: result
- name: Delete Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: devstack
state: absent
id: 1a85ebca-22bf-42eb-ad9e-f640789b8098
register: result
- name: Update Bare Metal deploy template
openstack.cloud.baremetal_deploy_template:
cloud: devstack
state: present
id: 1a85ebca-22bf-42eb-ad9e-f640789b8098
extra:
something: new
'''
RETURN = r'''
template:
description: A deploy template dictionary, subset of the dictionary keys
listed below may be returned, depending on your cloud
provider.
returned: success
type: dict
contains:
created_at:
description: Bare Metal deploy template created at timestamp.
returned: success
type: str
extra:
description: A set of one or more arbitrary metadata key and value
pairs.
returned: success
type: dict
id:
description: The UUID for the Baremetal Deploy Template resource.
returned: success
type: str
links:
description: A list of relative links, including the self and
bookmark links.
returned: success
type: list
location:
description: Cloud location of this resource (cloud, project,
region, zone)
returned: success
type: dict
name:
description: Bare Metal deploy template name.
returned: success
type: str
steps:
description: A list of deploy steps.
returned: success
type: list
elements: dict
updated_at:
description: Bare Metal deploy template updated at timestamp.
returned: success
type: str
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
OpenStackModule
)
class BaremetalDeployTemplateModule(OpenStackModule):
argument_spec = dict(
extra=dict(type='dict'),
id=dict(aliases=['uuid']),
name=dict(),
steps=dict(type='list', elements='dict'),
state=dict(default='present', choices=['present', 'absent']),
)
module_kwargs = dict(
required_one_of=[
('id', 'name'),
],
)
def run(self):
template = self._find_deploy_template()
state = self.params['state']
if state == 'present':
# create or update deploy template
kwargs = {}
for k in ['extra', 'id', 'name', 'steps']:
if self.params[k] is not None:
kwargs[k] = self.params[k]
changed = True
if not template:
# create deploy template
template = self.conn.baremetal.create_deploy_template(**kwargs)
else:
# update deploy template
updates = dict((k, v)
for k, v in kwargs.items()
if v != template[k])
if updates:
template = \
self.conn.baremetal.update_deploy_template(template['id'], **updates)
else:
changed = False
self.exit_json(changed=changed, template=template.to_dict(computed=False))
if state == 'absent':
# remove deploy template
if not template:
self.exit_json(changed=False)
template = self.conn.baremetal.delete_deploy_template(template['id'])
self.exit_json(changed=True)
def _find_deploy_template(self):
id_or_name = self.params['id'] if self.params['id'] else self.params['name']
try:
return self.conn.baremetal.get_deploy_template(id_or_name)
except self.sdk.exceptions.ResourceNotFound:
return None
def main():
module = BaremetalDeployTemplateModule()
module()
if __name__ == "__main__":
main()

View File

@@ -93,10 +93,10 @@ EXAMPLES = r'''
image_checksum: "356a6b55ecc511a20c33c946c4e678af"
image_disk_format: "qcow"
delegate_to: localhost
deploy: yes
deploy: true
cloud: "openstack"
config_drive: "http://192.168.1.1/host-configdrive.iso"
maintenance: no
maintenance: false
power: present
uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69"
state: present

View File

@@ -0,0 +1,257 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2026 OpenStack Ansible SIG
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
module: baremetal_port_group
short_description: Create/Delete Bare Metal port group resources from OpenStack
author: OpenStack Ansible SIG
description:
- Create, update and remove Bare Metal port groups from OpenStack.
options:
id:
description:
- ID of the port group.
- Will be auto-generated if not specified.
type: str
aliases: ['uuid']
name:
description:
- Name of the port group.
type: str
node:
description:
- ID or Name of the node this resource belongs to.
- Required when creating a new port group.
type: str
address:
description:
- Physical hardware address of this port group, typically the hardware
MAC address.
type: str
extra:
description:
- A set of one or more arbitrary metadata key and value pairs.
type: dict
standalone_ports_supported:
description:
- Whether the port group supports ports that are not members of this
port group.
type: bool
mode:
description:
- The port group mode.
type: str
properties:
description:
- Key/value properties for the port group.
type: dict
state:
description:
- Indicates desired state of the resource.
choices: ['present', 'absent']
default: present
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = r'''
- name: Create Bare Metal port group
openstack.cloud.baremetal_port_group:
cloud: devstack
state: present
name: bond0
node: bm-0
address: fa:16:3e:aa:aa:aa
mode: '802.3ad'
standalone_ports_supported: true
register: result
- name: Update Bare Metal port group
openstack.cloud.baremetal_port_group:
cloud: devstack
state: present
id: 1a85ebca-22bf-42eb-ad9e-f640789b8098
mode: 'active-backup'
properties:
miimon: '100'
register: result
- name: Delete Bare Metal port group
openstack.cloud.baremetal_port_group:
cloud: devstack
state: absent
id: 1a85ebca-22bf-42eb-ad9e-f640789b8098
register: result
'''
RETURN = r'''
port_group:
description: A port group dictionary, subset of the dictionary keys listed
below may be returned, depending on your cloud provider.
returned: success
type: dict
contains:
address:
description: Physical hardware address of the port group.
returned: success
type: str
created_at:
description: Bare Metal port group created at timestamp.
returned: success
type: str
extra:
description: A set of one or more arbitrary metadata key and value
pairs.
returned: success
type: dict
id:
description: The UUID for the Bare Metal port group resource.
returned: success
type: str
links:
description: A list of relative links, including the self and
bookmark links.
returned: success
type: list
mode:
description: The port group mode.
returned: success
type: str
name:
description: Bare Metal port group name.
returned: success
type: str
node_id:
description: UUID of the Bare Metal node this resource belongs to.
returned: success
type: str
properties:
description: Key/value properties for this port group.
returned: success
type: dict
standalone_ports_supported:
description: Whether standalone ports are supported.
returned: success
type: bool
updated_at:
description: Bare Metal port group updated at timestamp.
returned: success
type: str
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
OpenStackModule
)
class BaremetalPortGroupModule(OpenStackModule):
argument_spec = dict(
id=dict(aliases=['uuid']),
name=dict(),
node=dict(),
address=dict(),
extra=dict(type='dict'),
standalone_ports_supported=dict(type='bool'),
mode=dict(),
properties=dict(type='dict'),
state=dict(default='present', choices=['present', 'absent']),
)
module_kwargs = dict(
required_one_of=[
('id', 'name'),
],
supports_check_mode=True,
)
def _find_port_group(self):
id_or_name = self.params['id'] if self.params['id'] else self.params['name']
if not id_or_name:
return None
try:
return self.conn.baremetal.find_port_group(id_or_name)
except self.sdk.exceptions.ResourceNotFound:
return None
def _build_create_attrs(self):
attrs = {}
for key in ['id', 'name', 'address', 'extra',
'standalone_ports_supported', 'mode', 'properties']:
if self.params[key] is not None:
attrs[key] = self.params[key]
node_name_or_id = self.params['node']
if not node_name_or_id:
self.fail_json(msg="Parameter 'node' is required when creating a new port group")
node = self.conn.baremetal.find_node(node_name_or_id, ignore_missing=False)
attrs['node_id'] = node['id']
return attrs
def _build_update_attrs(self, port_group):
attrs = {}
for key in ['name', 'address', 'extra',
'standalone_ports_supported', 'mode', 'properties']:
if self.params[key] is not None and self.params[key] != port_group.get(key):
attrs[key] = self.params[key]
return attrs
def _will_change(self, port_group, state):
if state == 'absent':
return bool(port_group)
if not port_group:
return True
return bool(self._build_update_attrs(port_group))
def run(self):
state = self.params['state']
port_group = self._find_port_group()
if self.ansible.check_mode:
if state == 'present' and not port_group:
self._build_create_attrs()
self.exit_json(changed=self._will_change(port_group, state))
if state == 'present':
if not port_group:
port_group = self.conn.baremetal.create_port_group(
**self._build_create_attrs())
self.exit_json(
changed=True,
port_group=port_group.to_dict(computed=False))
update_attrs = self._build_update_attrs(port_group)
changed = bool(update_attrs)
if changed:
port_group = self.conn.baremetal.update_port_group(
port_group['id'], **update_attrs)
self.exit_json(
changed=changed,
port_group=port_group.to_dict(computed=False))
if not port_group:
self.exit_json(changed=False)
self.conn.baremetal.delete_port_group(port_group['id'])
self.exit_json(changed=True)
def main():
module = BaremetalPortGroupModule()
module()
if __name__ == "__main__":
main()

View File

@@ -80,6 +80,10 @@ options:
- Magnum's default value for I(is_registry_enabled) is C(false).
type: bool
aliases: ['registry_enabled']
insecure_registry:
description:
- The URL pointing to users own private insecure docker registry.
type: str
is_tls_disabled:
description:
- Indicates whether the TLS should be disabled.
@@ -317,7 +321,7 @@ EXAMPLES = r'''
image_id: 2a8c9888-9054-4b06-a1ca-2bb61f9adb72
keypair_id: mykey
name: k8s
is_public: no
is_public: false
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
@@ -342,6 +346,7 @@ class COEClusterTemplateModule(OpenStackModule):
keypair_id=dict(),
labels=dict(type='raw'),
master_flavor_id=dict(),
insecure_registry=dict(),
is_master_lb_enabled=dict(type='bool', default=False,
aliases=['master_lb_enabled']),
is_public=dict(type='bool', aliases=['public']),
@@ -412,6 +417,7 @@ class COEClusterTemplateModule(OpenStackModule):
'fixed_subnet', 'flavor_id',
'http_proxy', 'https_proxy',
'image_id',
'insecure_registry',
'is_floating_ip_enabled',
'is_master_lb_enabled',
'is_public', 'is_registry_enabled',
@@ -427,6 +433,9 @@ class COEClusterTemplateModule(OpenStackModule):
if isinstance(labels, str):
labels = dict([tuple(kv.split(":"))
for kv in labels.split(",")])
elif isinstance(labels, dict):
labels = dict({str(k): str(v)
for k, v in labels.items()})
if labels != cluster_template['labels']:
non_updateable_keys.append('labels')
@@ -458,7 +467,7 @@ class COEClusterTemplateModule(OpenStackModule):
'external_network_id', 'fixed_network',
'fixed_subnet', 'flavor_id', 'http_proxy',
'https_proxy', 'image_id',
'is_floating_ip_enabled',
'insecure_registry', 'is_floating_ip_enabled',
'is_master_lb_enabled', 'is_public',
'is_registry_enabled', 'is_tls_disabled',
'keypair_id', 'master_flavor_id', 'name',

View File

@@ -34,7 +34,7 @@ options:
type: str
choices: ['present', 'absent']
notes:
- A compute flavor must not be private to manage project access.
- A compute flavor must be private to manage project access.
extends_documentation_fragment:
- openstack.cloud.openstack
'''

View File

@@ -41,11 +41,11 @@ extends_documentation_fragment:
EXAMPLES = r'''
- name: Fetch all DNS zones
openstack.cloud.dns_zones:
openstack.cloud.dns_zone_info:
cloud: devstack
- name: Fetch DNS zones by name
openstack.cloud.dns_zones:
openstack.cloud.dns_zone_info:
cloud: devstack
name: ansible.test.zone.
'''

View File

@@ -42,7 +42,7 @@ options:
floating IP completely, or only detach it from the server.
Default is to detach only.
type: bool
default: 'no'
default: 'false'
reuse:
description:
- When I(state) is present, and I(floating_ip_address) is not present,
@@ -52,7 +52,7 @@ options:
I(floating_ip_address) is undefined, then C(nat_destination) and
C(fixed_address) will be ignored.
type: bool
default: 'no'
default: 'false'
server:
description:
- The name or ID of the server to which the IP address
@@ -83,7 +83,7 @@ EXAMPLES = '''
- openstack.cloud.floating_ip:
cloud: dguerri
state: present
reuse: yes
reuse: true
server: cattle001
network: ext_net
fixed_address: 192.0.2.3
@@ -244,7 +244,7 @@ class NetworkingFloatingIPModule(OpenStackModule):
else: # ip
# Requested floating ip address exists already
if ip.port_details and (ip.port_details.status == 'ACTIVE') \
if ip.port_details and (ip.port_details['status'] == 'ACTIVE') \
and (floating_ip_address not in self._filter_ips(
self.server)):
# Floating ip address exists and has been attached

View File

@@ -32,7 +32,7 @@ options:
description:
- Whether the user is enabled or not.
type: bool
default: 'yes'
default: 'true'
aliases: ['enabled']
name:
description:
@@ -56,7 +56,7 @@ options:
description:
- When I(update_password) is C(always), then the password will always be
updated.
- When I(update_password) is C(on_create), the the password is only set
- When I(update_password) is C(on_create), then the password is only set
when creating a user.
type: str
extends_documentation_fragment:

View File

@@ -100,8 +100,8 @@ options:
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
- Should the resource be present, absent or inactive.
choices: [present, absent, inactive]
default: present
type: str
tags:
@@ -122,6 +122,26 @@ options:
- I(volume) has been deprecated. Use module M(openstack.cloud.volume)
instead.
type: str
use_import:
description:
- Use the 'glance-direct' method of the interoperable image import mechanism.
- Should only be used when needed, such as when the user needs the cloud to
transform image format.
type: bool
import_method:
description:
- Method to use for importing the image. Not all deployments support all methods.
- Supports web-download or glance-download.
- copy-image is not supported with create actions.
- glance-direct is removed from the import method so use_import can be used in that case.
type: str
choices: [web-download, glance-download]
uri:
description:
- Required only if using the web-download import method.
- This url is where the data is made available to the Image service.
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
@@ -147,7 +167,7 @@ EXAMPLES = r'''
RETURN = r'''
image:
description: Dictionary describing the Glance image.
returned: On success when I(state) is C(present).
returned: On success when I(state) is C(present) or C(inactive).
type: dict
contains:
id:
@@ -388,15 +408,18 @@ class ImageModule(OpenStackModule):
owner_domain=dict(aliases=['project_domain']),
properties=dict(type='dict', default={}),
ramdisk=dict(),
state=dict(default='present', choices=['absent', 'present']),
state=dict(default='present', choices=['absent', 'present', 'inactive']),
tags=dict(type='list', default=[], elements='str'),
visibility=dict(choices=['public', 'private', 'shared', 'community']),
volume=dict(),
use_import=dict(type='bool'),
import_method=dict(choices=['web-download', 'glance-download']),
uri=dict()
)
module_kwargs = dict(
mutually_exclusive=[
('filename', 'volume'),
('filename', 'volume', 'uri'),
('visibility', 'is_public'),
],
)
@@ -404,7 +427,8 @@ class ImageModule(OpenStackModule):
# resource attributes obtainable directly from params
attr_params = ('id', 'name', 'filename', 'disk_format',
'container_format', 'wait', 'timeout', 'is_public',
'is_protected', 'min_disk', 'min_ram', 'volume', 'tags')
'is_protected', 'min_disk', 'min_ram', 'volume', 'tags',
'use_import', 'import_method', 'uri')
def _resolve_visibility(self):
"""resolve a visibility value to be compatible with older versions"""
@@ -485,7 +509,7 @@ class ImageModule(OpenStackModule):
if image_name_or_id:
image = self.conn.get_image(
image_name_or_id,
filters={(k, self.params[k])
filters={k: self.params[k]
for k in ['checksum'] if self.params[k] is not None})
changed = False
@@ -502,6 +526,26 @@ class ImageModule(OpenStackModule):
self.exit_json(changed=changed,
image=self._return_value(image.id))
if image['status'] == 'deactivated':
self.conn.image.reactivate_image(image)
changed = True
elif image['status'] == 'queued':
if (
self.params['filename']
and hasattr(self.conn.image, 'stage_image')):
self.conn.image.stage_image(
image, filename=self.params['filename'])
changed = True
elif self.params['filename']:
with open(self.params['filename'], 'rb') as image_data:
self.conn.image.upload_image(
container_format=self.params['container_format'],
disk_format=self.params['disk_format'],
data=image_data,
id=image.id,
name=image.name)
changed = True
update_payload = self._build_update(image)
if update_payload:
@@ -517,6 +561,20 @@ class ImageModule(OpenStackModule):
wait=self.params['wait'],
timeout=self.params['timeout'])
changed = True
elif self.params['state'] == 'inactive' and image is not None:
if image['status'] == 'active':
self.conn.image.deactivate_image(image)
changed = True
update_payload = self._build_update(image)
if update_payload:
self.conn.image.update_image(image.id, **update_payload)
changed = True
self.exit_json(changed=changed, image=self._return_value(image.id))
self.exit_json(changed=changed)

View File

@@ -142,7 +142,7 @@ pool:
'''
EXAMPLES = r'''
- name: Create a load-balander pool
- name: Create a load-balancer pool
openstack.cloud.lb_pool:
cloud: mycloud
lb_algorithm: ROUND_ROBIN
@@ -151,7 +151,7 @@ EXAMPLES = r'''
protocol: HTTP
state: present
- name: Delete a load-balander pool
- name: Delete a load-balancer pool
openstack.cloud.lb_pool:
cloud: mycloud
name: test-pool

View File

@@ -258,7 +258,7 @@ EXAMPLES = r'''
- name: Delete a load balancer, its related resources and its floating ip
openstack.cloud.loadbalancer:
cloud: devstack
delete_floating_ip: yes
delete_floating_ip: true
name: my_lb
state: absent
'''

View File

@@ -30,6 +30,15 @@ options:
description:
- Whether this network is externally accessible.
type: bool
is_default:
description:
- Whether this network is default network or not. This is only effective
with external networks.
type: bool
is_vlan_transparent:
description:
- Whether this network is vlan_transparent or not.
type: bool
state:
description:
- Indicate desired state of the resource.
@@ -190,6 +199,8 @@ class NetworkModule(OpenStackModule):
shared=dict(type='bool'),
admin_state_up=dict(type='bool'),
external=dict(type='bool'),
is_default=dict(type='bool'),
is_vlan_transparent=dict(type='bool'),
provider_physical_network=dict(),
provider_network_type=dict(),
provider_segmentation_id=dict(type='int'),
@@ -207,6 +218,8 @@ class NetworkModule(OpenStackModule):
shared = self.params['shared']
admin_state_up = self.params['admin_state_up']
external = self.params['external']
is_default = self.params['is_default']
is_vlan_transparent = self.params['is_vlan_transparent']
provider_physical_network = self.params['provider_physical_network']
provider_network_type = self.params['provider_network_type']
provider_segmentation_id = self.params['provider_segmentation_id']
@@ -244,6 +257,10 @@ class NetworkModule(OpenStackModule):
kwargs["admin_state_up"] = admin_state_up
if external is not None:
kwargs["is_router_external"] = external
if is_default is not None:
kwargs["is_default"] = is_default
if is_vlan_transparent is not None:
kwargs["is_vlan_transparent"] = is_vlan_transparent
if not net:
net = self.conn.network.create_network(name=name, **kwargs)

View File

@@ -0,0 +1,183 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2025 British Broadcasting Corporation
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: network_segment
short_description: Creates/removes network segments from OpenStack
author: OpenStack Ansible SIG
description:
- Add, update or remove network segments from OpenStack.
options:
name:
description:
- Name to be assigned to the segment. Although Neutron allows for
non-unique segment names, this module enforces segment name
uniqueness.
required: true
type: str
description:
description:
- Description of the segment
type: str
network:
description:
- Name or id of the network to which the segment should be attached
type: str
network_type:
description:
- The type of physical network that maps to this segment resource.
type: str
physical_network:
description:
- The physical network where this segment object is implemented.
type: str
segmentation_id:
description:
- An isolated segment on the physical network. The I(network_type)
attribute defines the segmentation model. For example, if the
I(network_type) value is vlan, this ID is a vlan identifier. If
the I(network_type) value is gre, this ID is a gre key.
type: int
state:
description:
- Indicate desired state of the resource.
choices: ['present', 'absent']
default: present
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a VLAN type network segment named 'segment1'.
- openstack.cloud.network_segment:
cloud: mycloud
name: segment1
network: my_network
network_type: vlan
segmentation_id: 2000
physical_network: my_physnet
state: present
'''
RETURN = '''
id:
description: Id of segment
returned: On success when segment exists.
type: str
network_segment:
description: Dictionary describing the network segment.
returned: On success when network segment exists.
type: dict
contains:
description:
description: Description
type: str
id:
description: Id
type: str
name:
description: Name
type: str
network_id:
description: Network Id
type: str
network_type:
description: Network type
type: str
physical_network:
description: Physical network
type: str
segmentation_id:
description: Segmentation Id
type: int
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class NetworkSegmentModule(OpenStackModule):
argument_spec = dict(
name=dict(required=True),
description=dict(),
network=dict(),
network_type=dict(),
physical_network=dict(),
segmentation_id=dict(type='int'),
state=dict(default='present', choices=['absent', 'present'])
)
def run(self):
state = self.params['state']
name = self.params['name']
network_name_or_id = self.params['network']
kwargs = {}
filters = {}
for arg in ('description', 'network_type', 'physical_network', 'segmentation_id'):
if self.params[arg] is not None:
kwargs[arg] = self.params[arg]
for arg in ('network_type', 'physical_network'):
if self.params[arg] is not None:
filters[arg] = self.params[arg]
if network_name_or_id:
network = self.conn.network.find_network(network_name_or_id,
ignore_missing=False,
**filters)
kwargs['network_id'] = network.id
filters['network_id'] = network.id
segment = self.conn.network.find_segment(name, **filters)
if state == 'present':
if not segment:
segment = self.conn.network.create_segment(name=name, **kwargs)
changed = True
else:
changed = False
update_kwargs = {}
# As the name is required and all other attributes cannot be
# changed (and appear in filters above), we only need to handle
# updates to the description here.
for arg in ["description"]:
if (
arg in kwargs
# ensure user wants something specific
and kwargs[arg] is not None
# and this is not what we have right now
and kwargs[arg] != segment[arg]
):
update_kwargs[arg] = kwargs[arg]
if update_kwargs:
segment = self.conn.network.update_segment(
segment.id, **update_kwargs
)
changed = True
segment = segment.to_dict(computed=False)
self.exit(changed=changed, network_segment=segment, id=segment['id'])
elif state == 'absent':
if not segment:
self.exit(changed=False)
else:
self.conn.network.delete_segment(segment['id'])
self.exit(changed=True)
def main():
module = NetworkSegmentModule()
module()
if __name__ == '__main__':
main()

View File

@@ -65,6 +65,12 @@ options:
- Required when creating or updating a RBAC policy rule, ignored when
deleting a policy.
type: str
target_all_project:
description:
- Whether all projects are targted for access.
- If this option set to true, C(target_project_id) is ignored.
type: bool
default: 'false'
state:
description:
- Whether the RBAC rule should be C(present) or C(absent).
@@ -145,6 +151,8 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class NeutronRBACPolicy(OpenStackModule):
all_project_symbol = '*'
argument_spec = dict(
action=dict(choices=['access_as_external', 'access_as_shared']),
id=dict(aliases=['policy_id']),
@@ -153,17 +161,22 @@ class NeutronRBACPolicy(OpenStackModule):
project_id=dict(),
state=dict(default='present', choices=['absent', 'present']),
target_project_id=dict(),
target_all_project=dict(type='bool', default=False),
)
module_kwargs = dict(
required_if=[
('state', 'present', ('target_project_id',)),
('state', 'present', ('target_project_id', 'target_all_project',), True),
('state', 'absent', ('id',)),
],
supports_check_mode=True,
)
def run(self):
target_all_project = self.params.get('target_all_project')
if target_all_project:
self.params['target_project_id'] = self.all_project_symbol
state = self.params['state']
policy = self._find()
@@ -262,7 +275,7 @@ class NeutronRBACPolicy(OpenStackModule):
return [p for p in policies
if any(p[k] == self.params[k]
for k in ['object_id', 'target_project_id'])]
for k in ['object_id'])]
def _update(self, policy, update):
attributes = update.get('attributes')

View File

@@ -295,8 +295,11 @@ class ObjectModule(OpenStackModule):
for k in ['data', 'filename']
if self.params[k] is not None)
return self.conn.object_store.create_object(container_name, name,
**kwargs)
object = self.conn.object_store.create_object(container_name, name,
**kwargs)
if not object:
object = self._find()
return object
def _delete(self, object):
container_name = self.params['container']

View File

@@ -269,7 +269,7 @@ class ContainerModule(OpenStackModule):
if metadata is not None:
# Swift metadata keys must be treated as case-insensitive
old_metadata = dict((k.lower(), v)
for k, v in (container.metadata or {}))
for k, v in (container.metadata or {}).items())
new_metadata = dict((k, v) for k, v in metadata.items()
if k.lower() not in old_metadata
or v != old_metadata[k.lower()])

View File

@@ -0,0 +1,202 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 Catalyst Cloud Limited
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r"""
---
module: object_containers_info
short_description: Fetch container info from the OpenStack Swift service.
author: OpenStack Ansible SIG
description:
- Fetch container info from the OpenStack Swift service.
options:
name:
description:
- Name of the container
type: str
aliases: ["container"]
prefix:
description:
- Filter containers by prefix
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
"""
EXAMPLES = r"""
- name: List all containers existing on the project
openstack.cloud.object_containers_info:
- name: Retrive a single container by name
openstack.cloud.object_containers_info:
name: test-container
- name: Retrieve and filter containers by prefix
openstack.cloud.object_containers_info:
prefix: test-
"""
RETURN = r"""
containers:
description: List of dictionaries describing matching containers.
returned: always
type: list
elements: dict
contains:
bytes:
description: The total number of bytes that are stored in Object Storage
for the container.
type: int
sample: 5449
bytes_used:
description: The count of bytes used in total.
type: int
sample: 5449
content_type:
description: The MIME type of the list of names.
Only fetched when searching for a container by name.
type: str
sample: null
count:
description: The number of objects in the container.
type: int
sample: 1
history_location:
description: Enables versioning on the container.
Only fetched when searching for a container by name.
type: str
sample: null
id:
description: The ID of the container. Equals I(name).
type: str
sample: "otc"
if_none_match:
description: "In combination with C(Expect: 100-Continue), specify an
C(If-None-Match: *) header to query whether the server
already has a copy of the object before any data is sent.
Only set when searching for a container by name."
type: str
sample: null
is_content_type_detected:
description: If set to C(true), Object Storage guesses the content type
based on the file extension and ignores the value sent in
the Content-Type header, if present.
Only fetched when searching for a container by name.
type: bool
sample: null
is_newest:
description: If set to True, Object Storage queries all replicas to
return the most recent one. If you omit this header, Object
Storage responds faster after it finds one valid replica.
Because setting this header to True is more expensive for
the back end, use it only when it is absolutely needed.
Only fetched when searching for a container by name.
type: bool
sample: null
meta_temp_url_key:
description: The secret key value for temporary URLs. If not set,
this header is not returned by this operation.
Only fetched when searching for a container by name.
type: str
sample: null
meta_temp_url_key_2:
description: A second secret key value for temporary URLs. If not set,
this header is not returned by this operation.
Only fetched when searching for a container by name.
type: str
sample: null
name:
description: The name of the container.
type: str
sample: "otc"
object_count:
description: The number of objects.
type: int
sample: 1
read_ACL:
description: The ACL that grants read access. If not set, this header is
not returned by this operation.
Only fetched when searching for a container by name.
type: str
sample: null
storage_policy:
description: Storage policy used by the container. It is not possible to
change policy of an existing container.
Only fetched when searching for a container by name.
type: str
sample: null
sync_key:
description: The secret key for container synchronization. If not set,
this header is not returned by this operation.
Only fetched when searching for a container by name.
type: str
sample: null
sync_to:
description: The destination for container synchronization. If not set,
this header is not returned by this operation.
Only fetched when searching for a container by name.
type: str
sample: null
timestamp:
description: The timestamp of the transaction.
Only fetched when searching for a container by name.
type: str
sample: null
versions_location:
description: Enables versioning on this container. The value is the name
of another container. You must UTF-8-encode and then
URL-encode the name before you include it in the header. To
disable versioning, set the header to an empty string.
Only fetched when searching for a container by name.
type: str
sample: null
write_ACL:
description: The ACL that grants write access. If not set, this header is
not returned by this operation.
Only fetched when searching for a container by name.
type: str
sample: null
"""
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class ObjectContainersInfoModule(OpenStackModule):
argument_spec = dict(
name=dict(aliases=["container"]),
prefix=dict(),
)
module_kwargs = dict(
supports_check_mode=True,
)
def run(self):
if self.params["name"]:
containers = [
(
self.conn.object_store.get_container_metadata(
self.params["name"],
).to_dict(computed=False)
),
]
else:
query = {}
if self.params["prefix"]:
query["prefix"] = self.params["prefix"]
containers = [
c.to_dict(computed=False)
for c in self.conn.object_store.containers(**query)
]
self.exit(changed=False, containers=containers)
def main():
module = ObjectContainersInfoModule()
module()
if __name__ == "__main__":
main()

View File

@@ -135,13 +135,14 @@ options:
description:
- Do not associate a security group with this port.
- "Deprecated. Use I(security_groups): C([]) instead
of I(no_security_groups): C(yes)."
of I(no_security_groups): C(true)."
type: bool
default: 'no'
port_security_enabled:
default: 'false'
is_port_security_enabled:
description:
- Whether to enable or disable the port security on the network.
type: bool
aliases: ['port_security_enabled']
security_groups:
description:
- Security group(s) ID(s) or name(s) associated with the port.
@@ -479,7 +480,7 @@ class PortModule(OpenStackModule):
name=dict(required=True),
network=dict(),
no_security_groups=dict(default=False, type='bool'),
port_security_enabled=dict(type='bool'),
is_port_security_enabled=dict(type='bool', aliases=['port_security_enabled']),
security_groups=dict(type='list', elements='str'),
state=dict(default='present', choices=['absent', 'present']),
)
@@ -510,7 +511,7 @@ class PortModule(OpenStackModule):
**(dict(network_id=network.id) if network else dict()))
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(network, port, state))
self.exit_json(changed=self._will_change(port, state))
if state == 'present' and not port:
# create port
@@ -655,7 +656,7 @@ class PortModule(OpenStackModule):
'extra_dhcp_opts',
'is_admin_state_up',
'mac_address',
'port_security_enabled',
'is_port_security_enabled',
'fixed_ips',
'name']:
if self.params[k] is not None:

View File

@@ -181,7 +181,7 @@ class IdentityProjectModule(OpenStackModule):
raise ValueError('Duplicate key(s) in extra_specs: {0}'
.format(', '.join(list(duplicate_keys))))
for k, v in extra_specs.items():
if v != project[k]:
if k not in project or v != project[k]:
attributes[k] = v
if attributes:

View File

@@ -38,6 +38,9 @@ options:
groups:
description: Number of groups that are allowed for the project
type: int
health_monitors:
description: Maximum number of health monitors that can be created.
type: int
injected_file_content_bytes:
description:
- Maximum file size in bytes.
@@ -61,6 +64,12 @@ options:
key_pairs:
description: Number of key pairs to allow.
type: int
l7_policies:
description: The maximum amount of L7 policies you can create.
type: int
listeners:
description: The maximum number of listeners you can create.
type: int
load_balancers:
description: The maximum amount of load balancers you can create
type: int
@@ -68,6 +77,9 @@ options:
metadata_items:
description: Number of metadata items allowed per instance.
type: int
members:
description: Number of members allowed for loadbalancer.
type: int
name:
description: Name of the OpenStack Project to manage.
required: true
@@ -227,6 +239,33 @@ quotas:
server_groups:
description: Number of server groups to allow.
type: int
load_balancer:
description: Load_balancer service quotas
type: dict
contains:
health_monitors:
description: Maximum number of health monitors that can be
created.
type: int
l7_policies:
description: The maximum amount of L7 policies you can
create.
type: int
listeners:
description: The maximum number of listeners you can create
type: int
load_balancers:
description: The maximum amount of load balancers one can
create
type: int
members:
description: The maximum amount of members for
loadbalancer.
type: int
pools:
description: The maximum amount of pools one can create.
type: int
network:
description: Network service quotas
type: dict
@@ -234,16 +273,9 @@ quotas:
floating_ips:
description: Number of floating IP's to allow.
type: int
load_balancers:
description: The maximum amount of load balancers one can
create
type: int
networks:
description: Number of networks to allow.
type: int
pools:
description: The maximum amount of pools one can create.
type: int
ports:
description: Number of Network ports to allow, this needs
to be greater than the instances limit.
@@ -312,9 +344,7 @@ quotas:
server_groups: 10,
network:
floating_ips: 50,
load_balancers: 10,
networks: 10,
pools: 10,
ports: 160,
rbac_policies: 10,
routers: 10,
@@ -330,6 +360,13 @@ quotas:
per_volume_gigabytes: -1,
snapshots: 10,
volumes: 10,
load_balancer:
health_monitors: 10,
load_balancers: 10,
l7_policies: 10,
listeners: 10,
pools: 5,
members: 5,
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
@@ -337,9 +374,8 @@ from collections import defaultdict
class QuotaModule(OpenStackModule):
# TODO: Add missing network quota options 'check_limit', 'health_monitors',
# 'l7_policies', 'listeners' to argument_spec, DOCUMENTATION and
# RETURN docstrings
# TODO: Add missing network quota options 'check_limit'
# to argument_spec, DOCUMENTATION and RETURN docstrings
argument_spec = dict(
backup_gigabytes=dict(type='int'),
backups=dict(type='int'),
@@ -350,6 +386,7 @@ class QuotaModule(OpenStackModule):
'network_floating_ips']),
gigabytes=dict(type='int'),
groups=dict(type='int'),
health_monitors=dict(type='int'),
injected_file_content_bytes=dict(type='int',
aliases=['injected_file_size']),
injected_file_path_bytes=dict(type='int',
@@ -357,8 +394,11 @@ class QuotaModule(OpenStackModule):
injected_files=dict(type='int'),
instances=dict(type='int'),
key_pairs=dict(type='int', no_log=False),
l7_policies=dict(type='int'),
listeners=dict(type='int'),
load_balancers=dict(type='int', aliases=['loadbalancer']),
metadata_items=dict(type='int'),
members=dict(type='int'),
name=dict(required=True),
networks=dict(type='int', aliases=['network']),
per_volume_gigabytes=dict(type='int'),
@@ -382,9 +422,9 @@ class QuotaModule(OpenStackModule):
supports_check_mode=True
)
# Some attributes in quota resources don't exist in the api anymore, mostly
# compute quotas that were simply network proxies. This map allows marking
# them to be skipped.
# Some attributes in quota resources don't exist in the api anymore, e.g.
# compute quotas that were simply network proxies, and pre-Octavia network
# quotas. This map allows marking them to be skipped.
exclusion_map = {
'compute': {
# 'fixed_ips', # Available until Nova API version 2.35
@@ -397,24 +437,39 @@ class QuotaModule(OpenStackModule):
# 'injected_file_path_bytes', # Nova API
# 'injected_files', # version 2.56
},
'network': {'name'},
'load_balancer': {'name'},
'network': {
'name',
'l7_policies',
'load_balancers',
'loadbalancer',
'health_monitors',
'pools',
'listeners',
},
'volume': {'name'},
}
def _get_quotas(self, project):
quota = {}
if self.conn.has_service('block-storage'):
quota['volume'] = self.conn.block_storage.get_quota_set(project)
quota['volume'] = self.conn.block_storage.get_quota_set(project.id)
else:
self.warn('Block storage service aka volume service is not'
' supported by your cloud. Ignoring volume quotas.')
if self.conn.has_service('load-balancer'):
quota['load_balancer'] = self.conn.load_balancer.get_quota(
project.id)
else:
self.warn('Loadbalancer service is not supported by your'
' cloud. Ignoring loadbalancer quotas.')
if self.conn.has_service('network'):
quota['network'] = self.conn.network.get_quota(project.id)
else:
self.warn('Network service is not supported by your cloud.'
' Ignoring network quotas.')
quota['compute'] = self.conn.compute.get_quota_set(project.id)
return quota
@@ -452,7 +507,6 @@ class QuotaModule(OpenStackModule):
# Get current quota values
quotas = self._get_quotas(project)
changed = False
if self.ansible.check_mode:
@@ -468,6 +522,8 @@ class QuotaModule(OpenStackModule):
self.conn.network.delete_quota(project.id)
if 'volume' in quotas:
self.conn.block_storage.revert_quota_set(project)
if 'load_balancer' in quotas:
self.conn.load_balancer.delete_quota(project.id)
# Necessary since we can't tell what the default quotas are
quotas = self._get_quotas(project)
@@ -477,14 +533,18 @@ class QuotaModule(OpenStackModule):
if changes:
if 'volume' in changes:
self.conn.block_storage.update_quota_set(
quotas['volume'], **changes['volume'])
quotas['volume'] = self.conn.block_storage.update_quota_set(
project.id, **changes['volume'])
if 'compute' in changes:
self.conn.compute.update_quota_set(
quotas['compute'], **changes['compute'])
quotas['compute'] = self.conn.compute.update_quota_set(
project.id, **changes['compute'])
if 'network' in changes:
quotas['network'] = self.conn.network.update_quota(
project.id, **changes['network'])
if 'load_balancer' in changes:
quotas['load_balancer'] = \
self.conn.load_balancer.update_quota(
project.id, **changes['load_balancer'])
changed = True
quotas = {k: v.to_dict(computed=False) for k, v in quotas.items()}

View File

@@ -181,6 +181,10 @@ class DnsRecordsetModule(OpenStackModule):
module_min_sdk_version = '0.28.0'
def _needs_update(self, params, recordset):
if params['records'] is not None:
params['records'] = sorted(params['records'])
if recordset['records'] is not None:
recordset['records'] = sorted(recordset['records'])
for k in ('description', 'records', 'ttl', 'type'):
if k not in params:
continue
@@ -235,7 +239,11 @@ class DnsRecordsetModule(OpenStackModule):
elif self._needs_update(kwargs, recordset):
recordset = self.conn.dns.update_recordset(recordset, **kwargs)
changed = True
self.exit_json(changed=changed, recordset=recordset)
# NOTE(gtema): this is a workaround to temporarily bring the
# zone_id param back which may not me populated by SDK
rs = recordset.to_dict(computed=False)
rs["zone_id"] = zone.id
self.exit_json(changed=changed, recordset=rs)
elif state == 'absent' and recordset is not None:
self.conn.dns.delete_recordset(recordset)
changed = True

View File

@@ -19,7 +19,9 @@ options:
- Valid only with keystone version 3.
- Required if I(project) is not specified.
- When I(project) is specified, then I(domain) will not be used for
scoping the role association, only for finding resources.
scoping the role association, only for finding resources. Deprecated
for finding resources, please use I(group_domain), I(project_domain),
I(role_domain), or I(user_domain).
- "When scoping the role association, I(project) has precedence over
I(domain) and I(domain) has precedence over I(system): When I(project)
is specified, then I(domain) and I(system) are not used for role
@@ -32,24 +34,45 @@ options:
- Valid only with keystone version 3.
- If I(group) is not specified, then I(user) is required. Both may not be
specified at the same time.
- You can supply I(group_domain) or the deprecated usage of I(domain) to
find group resources.
type: str
group_domain:
description:
- Name or ID for the domain.
- Valid only with keystone version 3.
- Only valid for finding group resources.
type: str
project:
description:
- Name or ID of the project to scope the role association to.
- If you are using keystone version 2, then this value is required.
- When I(project) is specified, then I(domain) will not be used for
scoping the role association, only for finding resources.
scoping the role association, only for finding resources. Prefer
I(group_domain) over I(domain).
- "When scoping the role association, I(project) has precedence over
I(domain) and I(domain) has precedence over I(system): When I(project)
is specified, then I(domain) and I(system) are not used for role
association. When I(domain) is specified, then I(system) will not be
used for role association."
type: str
project_domain:
description:
- Name or ID for the domain.
- Valid only with keystone version 3.
- Only valid for finding project resources.
type: str
role:
description:
- Name or ID for the role.
required: true
type: str
role_domain:
description:
- Name or ID for the domain.
- Valid only with keystone version 3.
- Only valid for finding role resources.
type: str
state:
description:
- Should the roles be present or absent on the user.
@@ -73,6 +96,12 @@ options:
- If I(user) is not specified, then I(group) is required. Both may not be
specified at the same time.
type: str
user_domain:
description:
- Name or ID for the domain.
- Valid only with keystone version 3.
- Only valid for finding user resources.
type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
@@ -101,11 +130,15 @@ class IdentityRoleAssignmentModule(OpenStackModule):
argument_spec = dict(
domain=dict(),
group=dict(),
group_domain=dict(type='str'),
project=dict(),
project_domain=dict(type='str'),
role=dict(required=True),
role_domain=dict(type='str'),
state=dict(default='present', choices=['absent', 'present']),
system=dict(),
user=dict(),
user_domain=dict(type='str'),
)
module_kwargs = dict(
@@ -113,17 +146,33 @@ class IdentityRoleAssignmentModule(OpenStackModule):
('user', 'group'),
('domain', 'project', 'system'),
],
mutually_exclusive=[
('user', 'group'),
('project', 'system'), # domain should be part of this
],
supports_check_mode=True
)
def _find_domain_id(self, domain):
if domain is not None:
domain = self.conn.identity.find_domain(domain,
ignore_missing=False)
return dict(domain_id=domain['id'])
return dict()
def run(self):
filters = {}
find_filters = {}
kwargs = {}
group_find_filters = {}
project_find_filters = {}
role_find_filters = {}
user_find_filters = {}
role_find_filters.update(self._find_domain_id(
self.params['role_domain']))
role_name_or_id = self.params['role']
role = self.conn.identity.find_role(role_name_or_id,
ignore_missing=False)
ignore_missing=False,
**role_find_filters)
filters['role_id'] = role['id']
domain_name_or_id = self.params['domain']
@@ -131,22 +180,31 @@ class IdentityRoleAssignmentModule(OpenStackModule):
domain = self.conn.identity.find_domain(
domain_name_or_id, ignore_missing=False)
filters['scope_domain_id'] = domain['id']
find_filters['domain_id'] = domain['id']
kwargs['domain'] = domain['id']
group_find_filters['domain_id'] = domain['id']
project_find_filters['domain_id'] = domain['id']
user_find_filters['domain_id'] = domain['id']
user_name_or_id = self.params['user']
if user_name_or_id is not None:
user_find_filters.update(self._find_domain_id(
self.params['user_domain']))
user = self.conn.identity.find_user(
user_name_or_id, ignore_missing=False, **find_filters)
user_name_or_id, ignore_missing=False,
**user_find_filters)
filters['user_id'] = user['id']
kwargs['user'] = user['id']
else:
user = None
group_name_or_id = self.params['group']
if group_name_or_id is not None:
group_find_filters.update(self._find_domain_id(
self.params['group_domain']))
group = self.conn.identity.find_group(
group_name_or_id, ignore_missing=False, **find_filters)
group_name_or_id, ignore_missing=False,
**group_find_filters)
filters['group_id'] = group['id']
kwargs['group'] = group['id']
else:
group = None
system_name = self.params['system']
if system_name is not None:
@@ -154,14 +212,14 @@ class IdentityRoleAssignmentModule(OpenStackModule):
if 'scope_domain_id' not in filters:
filters['scope.system'] = system_name
kwargs['system'] = system_name
project_name_or_id = self.params['project']
if project_name_or_id is not None:
project_find_filters.update(self._find_domain_id(
self.params['project_domain']))
project = self.conn.identity.find_project(
project_name_or_id, ignore_missing=False, **find_filters)
project_name_or_id, ignore_missing=False,
**project_find_filters)
filters['scope_project_id'] = project['id']
kwargs['project'] = project['id']
# project has precedence over domain and system
filters.pop('scope_domain_id', None)
@@ -176,10 +234,50 @@ class IdentityRoleAssignmentModule(OpenStackModule):
or (state == 'absent' and role_assignments)))
if state == 'present' and not role_assignments:
self.conn.grant_role(role['id'], **kwargs)
if 'scope_domain_id' in filters:
if user is not None:
self.conn.identity.assign_domain_role_to_user(
filters['scope_domain_id'], user, role)
else:
self.conn.identity.assign_domain_role_to_group(
filters['scope_domain_id'], group, role)
elif 'scope_project_id' in filters:
if user is not None:
self.conn.identity.assign_project_role_to_user(
filters['scope_project_id'], user, role)
else:
self.conn.identity.assign_project_role_to_group(
filters['scope_project_id'], group, role)
elif 'scope.system' in filters:
if user is not None:
self.conn.identity.assign_system_role_to_user(
user, role, filters['scope.system'])
else:
self.conn.identity.assign_system_role_to_group(
group, role, filters['scope.system'])
self.exit_json(changed=True)
elif state == 'absent' and role_assignments:
self.conn.revoke_role(role['id'], **kwargs)
if 'scope_domain_id' in filters:
if user is not None:
self.conn.identity.unassign_domain_role_from_user(
filters['scope_domain_id'], user, role)
else:
self.conn.identity.unassign_domain_role_from_group(
filters['scope_domain_id'], group, role)
elif 'scope_project_id' in filters:
if user is not None:
self.conn.identity.unassign_project_role_from_user(
filters['scope_project_id'], user, role)
else:
self.conn.identity.unassign_project_role_from_group(
filters['scope_project_id'], group, role)
elif 'scope.system' in filters:
if user is not None:
self.conn.identity.unassign_system_role_from_user(
user, role, filters['scope.system'])
else:
self.conn.identity.unassign_system_role_from_group(
group, role, filters['scope.system'])
self.exit_json(changed=True)
else:
self.exit_json(changed=False)

View File

@@ -87,7 +87,7 @@ options:
description:
- Desired admin state of the created or existing router.
type: bool
default: 'yes'
default: 'true'
aliases: ['admin_state_up']
name:
description:
@@ -366,38 +366,40 @@ class RouterModule(OpenStackModule):
if 'ip_address' in p:
cur_fip_map[p['subnet_id']].add(p['ip_address'])
req_fip_map = defaultdict(set)
for p in external_fixed_ips:
if 'ip_address' in p:
req_fip_map[p['subnet_id']].add(p['ip_address'])
if external_fixed_ips is not None:
# User passed expected external_fixed_ips configuration.
# Build map of requested ips/subnets.
for p in external_fixed_ips:
if 'ip_address' in p:
req_fip_map[p['subnet_id']].add(p['ip_address'])
elif p['subnet_id'] in cur_fip_map:
# handle idempotence of updating with no explicit ip
req_fip_map[p['subnet_id']].update(
cur_fip_map[p['subnet_id']])
# Check if external ip addresses need to be added
for fip in external_fixed_ips:
subnet = fip['subnet_id']
ip = fip.get('ip_address', None)
if subnet in cur_fip_map:
if ip is not None and ip not in cur_fip_map[subnet]:
# mismatching ip for subnet
# Check if external ip addresses need to be added
for fip in external_fixed_ips:
subnet = fip['subnet_id']
ip = fip.get('ip_address', None)
if subnet in cur_fip_map:
if ip is not None and ip not in cur_fip_map[subnet]:
# mismatching ip for subnet
return True
else:
# adding ext ip with subnet 'subnet'
return True
else:
# adding ext ip with subnet 'subnet'
return True
# Check if external ip addresses need to be removed
for fip in cur_ext_fips:
subnet = fip['subnet_id']
ip = fip['ip_address']
if subnet in req_fip_map:
if ip not in req_fip_map[subnet]:
# removing ext ip with subnet (ip clash)
# Check if external ip addresses need to be removed.
for fip in cur_ext_fips:
subnet = fip['subnet_id']
ip = fip['ip_address']
if subnet in req_fip_map:
if ip not in req_fip_map[subnet]:
# removing ext ip with subnet (ip clash)
return True
else:
# removing ext ip with subnet
return True
else:
# removing ext ip with subnet
return True
if not external_fixed_ips and len(cur_ext_fips) > 1:
# No external fixed ips requested but
# router has several external fixed ips
return True
# Check if internal interfaces need update
if to_add or to_remove or missing_port_ids:
@@ -448,7 +450,8 @@ class RouterModule(OpenStackModule):
return kwargs
def _build_router_interface_config(self, filters):
external_fixed_ips = []
# Undefine external_fixed_ips to have possibility to unset them
external_fixed_ips = None
internal_ports_missing = []
internal_ifaces = []
@@ -459,11 +462,13 @@ class RouterModule(OpenStackModule):
.get('external_fixed_ips')
ext_fixed_ips = ext_fixed_ips or self.params['external_fixed_ips']
if ext_fixed_ips:
# User passed external_fixed_ips configuration. Initialize ips list
external_fixed_ips = []
for iface in ext_fixed_ips:
subnet = self.conn.network.find_subnet(
iface['subnet'], ignore_missing=False, **filters)
iface['subnet_id'], ignore_missing=False, **filters)
fip = dict(subnet_id=subnet.id)
if 'ip_address' in iface:
if iface.get('ip_address', None) is not None:
fip['ip_address'] = iface['ip_address']
external_fixed_ips.append(fip)
@@ -615,9 +620,13 @@ class RouterModule(OpenStackModule):
router = self.conn.network.find_router(name, **query_filters)
network = None
if network_name_or_id:
# First try to find a network in the specified project.
network = self.conn.network.find_network(network_name_or_id,
ignore_missing=False,
**query_filters)
if not network:
# Fall back to a global search for the network.
network = self.conn.network.find_network(network_name_or_id,
ignore_missing=False)
# Validate and cache the subnet IDs so we can avoid duplicate checks
# and expensive API calls.

Some files were not shown because too many files have changed in this diff Show More