diff --git a/test/integration/Makefile b/test/integration/Makefile
index e2da6f46fa8..48382cd4c49 100644
--- a/test/integration/Makefile
+++ b/test/integration/Makefile
@@ -1,3 +1,4 @@
+TEST_DIR ?= ~/ansible_testing
INVENTORY ?= inventory
VARS_FILE ?= integration_config.yml
@@ -20,38 +21,87 @@ MYTMPDIR = $(shell mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
VAULT_PASSWORD_FILE = vault-password
CONSUL_RUNNING := $(shell python consul_running.py)
+EUID := $(shell id -u -r)
-all: parsing test_var_precedence unicode test_templating_settings environment non_destructive destructive includes blocks check_mode test_hash test_handlers test_group_by test_vault test_tags test_lookup_paths no_log
+all: setup test_test_infra parsing test_var_precedence unicode test_templating_settings environment non_destructive destructive includes blocks pull check_mode test_hash test_handlers test_group_by test_vault test_tags test_lookup_paths no_log test_connection test_gathering_facts
-parsing:
- ansible-playbook bad_parsing.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -vvv $(TEST_FLAGS) --tags prepare,common,scenario5
- ansible-playbook good_parsing.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+test_test_infra:
+ # ensure fail/assert work locally and can stop execution with non-zero exit code
+ PB_OUT=$$(ansible-playbook -i inventory.local test_test_infra.yml) ; APB_RC=$$? ; echo "$$PB_OUT" ; echo "rc was $$APB_RC (must be non-zero)" ; [ $$APB_RC -ne 0 ] ; echo "ensure playbook output shows assert/fail works (True)" ; echo "$$PB_OUT" | grep "fail works (True)" || exit 1 ; echo "$$PB_OUT" | fgrep "assert works (True)" || exit 1
+ # ensure we work using all specified test args, overridden inventory, etc
+ PB_OUT=$$(ansible-playbook -i $(INVENTORY) test_test_infra.yml -e @$(VARS_FILE) $(CREDENTIALS_ARG) $(TEST_FLAGS)) ; APB_RC=$$? ; echo "$$PB_OUT" ; echo "rc was $$APB_RC (must be non-zero)" ; [ $$APB_RC -ne 0 ] ; echo "ensure playbook output shows assert/fail works (True)" ; echo "$$PB_OUT" | grep "fail works (True)" || exit 1 ; echo "$$PB_OUT" | fgrep "assert works (True)" || exit 1
-includes:
- ansible-playbook test_includes.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) $(TEST_FLAGS)
+setup:
+ rm -rf $(TEST_DIR)
+ mkdir -p $(TEST_DIR)
-unicode:
- ansible-playbook unicode.yml -i $(INVENTORY) -e @$(VARS_FILE) -v $(TEST_FLAGS) -e 'extra_var=café'
+parsing: setup
+ ansible-playbook bad_parsing.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -vvv $(TEST_FLAGS) --tags prepare,common,scenario5
+ ansible-playbook good_parsing.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+
+includes: setup
+ ansible-playbook test_includes.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) $(TEST_FLAGS)
+
+pull: pull_run pull_no_127 pull_limit_inventory
+
+pull_run:
+ ansible-pull -d $(MYTMPDIR) -U https://github.com/ansible-test-robinro/pull-integration-test.git | grep MAGICKEYWORD; \
+ RC=$$? ; \
+ rm -rf $(MYTMPDIR); \
+ exit $$RC
+
+# test for https://github.com/ansible/ansible/issues/13681
+pull_no_127:
+ ansible-pull -d $(MYTMPDIR) -U https://github.com/ansible-test-robinro/pull-integration-test.git | grep -v 127\.0\.0\.1; \
+ RC=$$? ; \
+ rm -rf $(MYTMPDIR); \
+ exit $$RC
+
+# test for https://github.com/ansible/ansible/issues/13688
+pull_limit_inventory:
+ ansible-pull -d $(MYTMPDIR) -U https://github.com/ansible-test-robinro/pull-integration-test.git; \
+ RC=$$? ; \
+ rm -rf $(MYTMPDIR); \
+ exit $$RC
+
+
+unicode: setup
+ ansible-playbook unicode.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) -v $(TEST_FLAGS) -e 'extra_var=café'
# Test the start-at-task flag #9571
- ansible-playbook unicode.yml -i $(INVENTORY) -e @$(VARS_FILE) -v --start-at-task '*¶' -e 'start_at_task=True' $(TEST_FLAGS)
+ ansible-playbook unicode.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) -v --start-at-task '*¶' -e 'start_at_task=True' $(TEST_FLAGS)
+
+test_templating_settings: setup
+ ansible-playbook test_templating_settings.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
-test_templating_settings:
- ansible-playbook test_templating_settings.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+test_gathering_facts: setup
+ ansible-playbook test_gathering_facts.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) -v $(TEST_FLAGS)
+
+environment: setup
+ ansible-playbook test_environment.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) $(TEST_FLAGS)
+
+non_destructive: setup
+ ansible-playbook non_destructive.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+
+# Skip connection plugins which require root when not running as root.
+ifneq ($(EUID),0)
+TEST_CONNECTION_FILTER := !chroot
+endif
-environment:
- ansible-playbook test_environment.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) $(TEST_FLAGS)
+# Connection plugin test command to repeat with each locale setting.
+TEST_CONNECTION_CMD = $(1) ansible-playbook test_connection.yml -i test_connection.inventory -l '!skip-during-build $(TEST_CONNECTION_FILTER)' $(TEST_FLAGS)
-non_destructive:
- ansible-playbook non_destructive.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+test_connection: setup
+ $(call TEST_CONNECTION_CMD)
+ $(call TEST_CONNECTION_CMD, LC_ALL=C LANG=C)
-destructive:
- ansible-playbook destructive.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+destructive: setup
+ ansible-playbook destructive.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
-check_mode:
- ansible-playbook check_mode.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v --check $(TEST_FLAGS)
+check_mode: setup
+ ansible-playbook check_mode.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v --check $(TEST_FLAGS)
-test_group_by:
- ansible-playbook test_group_by.yml -i inventory.group_by -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+test_group_by: setup
+ ansible-playbook test_group_by.yml -i inventory.group_by -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
test_handlers:
ansible-playbook test_handlers.yml --tags scenario1 -i inventory.handlers -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
@@ -75,14 +125,14 @@ test_hash:
ANSIBLE_HASH_BEHAVIOUR=replace ansible-playbook test_hash.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v -e '{"test_hash":{"extra_args":"this is an extra arg"}}'
ANSIBLE_HASH_BEHAVIOUR=merge ansible-playbook test_hash.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v -e '{"test_hash":{"extra_args":"this is an extra arg"}}'
-test_var_precedence:
- ansible-playbook test_var_precedence.yml -i $(INVENTORY) $(CREDENTIALS_ARG) $(TEST_FLAGS) -v -e 'extra_var=extra_var' -e 'extra_var_override=extra_var_override'
+test_var_precedence: setup
+ ansible-playbook test_var_precedence.yml -i $(INVENTORY) $(CREDENTIALS_ARG) $(TEST_FLAGS) -v -e outputdir=$(TEST_DIR) -e 'extra_var=extra_var' -e 'extra_var_override=extra_var_override'
-test_vault:
- ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --list-tasks
- ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --list-hosts
- ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --syntax-check
- ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE)
+test_vault: setup
+ ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --list-tasks -e outputdir=$(TEST_DIR)
+ ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --list-hosts -e outputdir=$(TEST_DIR)
+ ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --syntax-check -e outputdir=$(TEST_DIR)
+ ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) -e outputdir=$(TEST_DIR)
# test_delegate_to does not work unless we have permission to ssh to localhost.
# Would take some more effort on our test systems to implement that -- probably
@@ -90,31 +140,31 @@ test_vault:
# root user on a node to ssh to itself. Until then, this is not in make all.
# Have to run it manually. Ordinary users should be able to run this test as
# long as they have permissions to login to their local machine via ssh.
-test_delegate_to:
- ansible-playbook test_delegate_to.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+test_delegate_to: setup
+ ansible-playbook test_delegate_to.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
-test_winrm:
- ansible-playbook test_winrm.yml -i inventory.winrm -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
+test_winrm: setup
+ ansible-playbook test_winrm.yml -i inventory.winrm -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
-test_tags:
+test_tags: setup
# Run everything by default
- [ "$$(ansible-playbook --list-tasks test_tags.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_tag TAGS: [tag] Task_with_always_tag TAGS: [always] Task_without_tag TAGS: []" ]
+ [ "$$(ansible-playbook --list-tasks test_tags.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_tag TAGS: [tag] Task_with_always_tag TAGS: [always] Task_without_tag TAGS: []" ]
# Run the exact tags, and always
- [ "$$(ansible-playbook --list-tasks --tags tag test_tags.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_tag TAGS: [tag] Task_with_always_tag TAGS: [always]" ]
+ [ "$$(ansible-playbook --list-tasks --tags tag test_tags.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_tag TAGS: [tag] Task_with_always_tag TAGS: [always]" ]
# Skip one tag
- [ "$$(ansible-playbook --list-tasks --skip-tags tag test_tags.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_always_tag TAGS: [always] Task_without_tag TAGS: []" ]
+ [ "$$(ansible-playbook --list-tasks --skip-tags tag test_tags.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) | fgrep Task_with | xargs)" = "Task_with_always_tag TAGS: [always] Task_without_tag TAGS: []" ]
-blocks:
+blocks: setup
# remove old output log
rm -f block_test.out
# run the test and check to make sure the right number of completions was logged
- ansible-playbook -vv test_blocks/main.yml | tee block_test.out
- [ "$$(grep 'TEST COMPLETE' block_test.out | wc -l)" == "$$(egrep '^[0-9]+ plays in' block_test.out | cut -f1 -d' ')" ]
+ ansible-playbook -vv -e outputdir=$(TEST_DIR) test_blocks/main.yml | tee block_test.out
+ [ "$$(grep 'TEST COMPLETE' block_test.out | wc -l)" = "$$(egrep '^[0-9]+ plays in' block_test.out | cut -f1 -d' ')" ]
# cleanup the output log again, to make sure the test is clean
rm -f block_test.out
# run test with free strategy and again count the completions
- ansible-playbook -vv test_blocks/main.yml -e test_strategy=free | tee block_test.out
- [ "$$(grep 'TEST COMPLETE' block_test.out | wc -l)" == "$$(egrep '^[0-9]+ plays in' block_test.out | cut -f1 -d' ')" ]
+ ansible-playbook -vv -e outputdir=$(TEST_DIR) test_blocks/main.yml -e test_strategy=free | tee block_test.out
+ [ "$$(grep 'TEST COMPLETE' block_test.out | wc -l)" = "$$(egrep '^[0-9]+ plays in' block_test.out | cut -f1 -d' ')" ]
cloud: amazon rackspace azure
@@ -181,25 +231,25 @@ endif
test_galaxy: test_galaxy_spec test_galaxy_yaml test_galaxy_git
-test_galaxy_spec:
+test_galaxy_spec: setup
mytmpdir=$(MYTMPDIR) ; \
ansible-galaxy install -r galaxy_rolesfile -p $$mytmpdir/roles -vvvv ; \
cp galaxy_playbook.yml $$mytmpdir ; \
- ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \
+ ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -e outputdir=$(TEST_DIR) -v $(TEST_FLAGS) ; \
RC=$$? ; \
rm -rf $$mytmpdir ; \
exit $$RC
-test_galaxy_yaml:
+test_galaxy_yaml: setup
mytmpdir=$(MYTMPDIR) ; \
ansible-galaxy install -r galaxy_roles.yml -p $$mytmpdir/roles -vvvv; \
cp galaxy_playbook.yml $$mytmpdir ; \
- ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -v $(TEST_FLAGS) ; \
+ ansible-playbook -i $(INVENTORY) $$mytmpdir/galaxy_playbook.yml -e outputdir=$(TEST_DIR) -v $(TEST_FLAGS) ; \
RC=$$? ; \
rm -rf $$mytmpdir ; \
exit $$RC
-test_galaxy_git:
+test_galaxy_git: setup
mytmpdir=$(MYTMPDIR) ; \
ansible-galaxy install git+https://bitbucket.org/willthames/git-ansible-galaxy,v1.6 -p $$mytmpdir/roles -vvvv; \
cp galaxy_playbook_git.yml $$mytmpdir ; \
@@ -208,9 +258,9 @@ test_galaxy_git:
rm -rf $$mytmpdir ; \
exit $$RC
-test_lookup_paths:
- ansible-playbook lookup_paths/play.yml -i $(INVENTORY) -v $(TEST_FLAGS)
+test_lookup_paths: setup
+ ansible-playbook lookup_paths/play.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -v $(TEST_FLAGS)
-no_log:
+no_log: setup
# This test expects 7 loggable vars and 0 non loggable ones, if either mismatches it fails, run the ansible-playbook command to debug
- [ "$$(ansible-playbook no_log_local.yml -i $(INVENTORY) -vvvvv | awk --source 'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "6/0" ]
+ [ "$$(ansible-playbook no_log_local.yml -i $(INVENTORY) -e outputdir=$(TEST_DIR) -vvvvv | awk --source 'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "6/0" ]
diff --git a/test/integration/cloudstack.yml b/test/integration/cloudstack.yml
index 93ba7876d8c..9104237bfd1 100644
--- a/test/integration/cloudstack.yml
+++ b/test/integration/cloudstack.yml
@@ -22,3 +22,9 @@
- { role: test_cs_account, tags: test_cs_account }
- { role: test_cs_firewall, tags: test_cs_firewall }
- { role: test_cs_loadbalancer_rule, tags: test_cs_loadbalancer_rule }
+ - { role: test_cs_volume, tags: test_cs_volume }
+ - { role: test_cs_instance_facts, tags: test_cs_instance_facts }
+ - { role: test_cs_configuration, tags: test_cs_configuration }
+ - { role: test_cs_pod, tags: test_cs_pod }
+ - { role: test_cs_cluster, tags: test_cs_cluster }
+ - { role: test_cs_resourcelimit, tags: test_cs_resourcelimit }
diff --git a/test/integration/galaxy_roles.yml b/test/integration/galaxy_roles.yml
index 5f4373c5004..3d2121f1683 100644
--- a/test/integration/galaxy_roles.yml
+++ b/test/integration/galaxy_roles.yml
@@ -3,7 +3,7 @@
name: oracle_java7
- src: git+http://bitbucket.org/willthames/git-ansible-galaxy
- version: v1.6
+ version: pr-10620
- src: http://bitbucket.org/willthames/hg-ansible-galaxy
scm: hg
diff --git a/test/integration/galaxy_rolesfile b/test/integration/galaxy_rolesfile
index b78cdc11481..047eef95502 100644
--- a/test/integration/galaxy_rolesfile
+++ b/test/integration/galaxy_rolesfile
@@ -1,7 +1,7 @@
# deliberate non-empty whitespace line to follow
-git+https://bitbucket.org/willthames/git-ansible-galaxy,v1.6
+git+https://bitbucket.org/willthames/git-ansible-galaxy,pr-10620
hg+https://bitbucket.org/willthames/hg-ansible-galaxy
https://bitbucket.org/willthames/http-ansible-galaxy/get/master.tar.gz,,http-role
# comment
diff --git a/test/integration/inventory b/test/integration/inventory
index bee36ce022e..427aa3dc852 100644
--- a/test/integration/inventory
+++ b/test/integration/inventory
@@ -4,6 +4,9 @@ testhost2 ansible_ssh_host=127.0.0.1 ansible_connection=local
# For testing delegate_to
testhost3 ansible_ssh_host=127.0.0.3
testhost4 ansible_ssh_host=127.0.0.4
+# For testing fact gathering
+facthost[0:8] ansible_host=1270.0.0.1 ansible_connection=local
+
# the following inline declarations are accompanied
# by (preferred) group_vars/ and host_vars/ variables
diff --git a/test/integration/inventory.local b/test/integration/inventory.local
new file mode 100644
index 00000000000..2baa1f88fbe
--- /dev/null
+++ b/test/integration/inventory.local
@@ -0,0 +1,2 @@
+testhost ansible_connection=local
+
diff --git a/test/integration/roles/prepare_tests/tasks/main.yml b/test/integration/roles/prepare_tests/tasks/main.yml
index 7983ea52361..a0af45cdf10 100644
--- a/test/integration/roles/prepare_tests/tasks/main.yml
+++ b/test/integration/roles/prepare_tests/tasks/main.yml
@@ -17,16 +17,15 @@
# along with Ansible. If not, see .
-- name: clean out the test directory
- file: name={{output_dir|mandatory}} state=absent
- always_run: True
- tags:
- - prepare
- when: clean_working_dir|default("yes")|bool
-
-- name: create the test directory
- file: name={{output_dir}} state=directory
- always_run: True
- tags:
- - prepare
-
+#- name: clean out the test directory
+# file: name={{output_dir|mandatory}} state=absent
+# always_run: True
+# tags:
+# - prepare
+# when: clean_working_dir|default("yes")|bool
+#
+#- name: create the test directory
+# file: name={{output_dir}} state=directory
+# always_run: True
+# tags:
+# - prepare
diff --git a/test/integration/roles/setup_mysql_db/tasks/main.yml b/test/integration/roles/setup_mysql_db/tasks/main.yml
index 612d94f6d11..d30f6967cb4 100644
--- a/test/integration/roles/setup_mysql_db/tasks/main.yml
+++ b/test/integration/roles/setup_mysql_db/tasks/main.yml
@@ -28,17 +28,17 @@
- name: install mysqldb_test rpm dependencies
yum: name={{ item }} state=latest
- with_items: mysql_packages
+ with_items: "{{mysql_packages}}"
when: ansible_pkg_mgr == 'yum'
- name: install mysqldb_test rpm dependencies
dnf: name={{ item }} state=latest
- with_items: mysql_packages
+ with_items: "{{mysql_packages}}"
when: ansible_pkg_mgr == 'dnf'
- name: install mysqldb_test debian dependencies
apt: name={{ item }} state=latest
- with_items: mysql_packages
+ with_items: "{{mysql_packages}}"
when: ansible_pkg_mgr == 'apt'
- name: start mysql_db service if not running
diff --git a/test/integration/roles/setup_postgresql_db/tasks/main.yml b/test/integration/roles/setup_postgresql_db/tasks/main.yml
index c25318a2adc..48f9211e1b4 100644
--- a/test/integration/roles/setup_postgresql_db/tasks/main.yml
+++ b/test/integration/roles/setup_postgresql_db/tasks/main.yml
@@ -10,12 +10,12 @@
# Make sure we start fresh
- name: remove rpm dependencies for postgresql test
package: name={{ item }} state=absent
- with_items: postgresql_packages
+ with_items: "{{postgresql_packages}}"
when: ansible_os_family == "RedHat"
- name: remove dpkg dependencies for postgresql test
apt: name={{ item }} state=absent
- with_items: postgresql_packages
+ with_items: "{{postgresql_packages}}"
when: ansible_pkg_mgr == 'apt'
- name: remove old db (red hat)
@@ -36,12 +36,12 @@
- name: install rpm dependencies for postgresql test
package: name={{ item }} state=latest
- with_items: postgresql_packages
+ with_items: "{{postgresql_packages}}"
when: ansible_os_family == "RedHat"
- name: install dpkg dependencies for postgresql test
apt: name={{ item }} state=latest
- with_items: postgresql_packages
+ with_items: "{{postgresql_packages}}"
when: ansible_pkg_mgr == 'apt'
- name: Initialize postgres (systemd)
diff --git a/test/integration/roles/test_apt_repository/tasks/apt.yml b/test/integration/roles/test_apt_repository/tasks/apt.yml
index 49d13bc52a3..9c8e3ab4473 100644
--- a/test/integration/roles/test_apt_repository/tasks/apt.yml
+++ b/test/integration/roles/test_apt_repository/tasks/apt.yml
@@ -2,6 +2,7 @@
- set_fact:
test_ppa_name: 'ppa:menulibre-dev/devel'
+ test_ppa_filename: 'menulibre-dev'
test_ppa_spec: 'deb http://ppa.launchpad.net/menulibre-dev/devel/ubuntu {{ansible_distribution_release}} main'
test_ppa_key: 'A7AD98A1' # http://keyserver.ubuntu.com:11371/pks/lookup?search=0xD06AAF4C11DAB86DF421421EFE6B20ECA7AD98A1&op=index
@@ -144,6 +145,47 @@
- name: 'ensure ppa key is absent (expect: pass)'
apt_key: id='{{test_ppa_key}}' state=absent
+#
+# TEST: apt_repository: repo= filename=
+#
+- include: 'cleanup.yml'
+
+- name: 'record apt cache mtime'
+ stat: path='/var/cache/apt/pkgcache.bin'
+ register: cache_before
+
+- name: 'name= filename= (expect: pass)'
+ apt_repository: repo='{{test_ppa_spec}}' filename='{{test_ppa_filename}}' state=present
+ register: result
+
+- assert:
+ that:
+ - 'result.changed'
+ - 'result.state == "present"'
+ - 'result.repo == "{{test_ppa_spec}}"'
+
+- name: 'examine source file'
+ stat: path='/etc/apt/sources.list.d/{{test_ppa_filename}}.list'
+ register: source_file
+
+- name: 'assert source file exists'
+ assert:
+ that:
+ - 'source_file.stat.exists == True'
+
+- name: 'examine apt cache mtime'
+ stat: path='/var/cache/apt/pkgcache.bin'
+ register: cache_after
+
+- name: 'assert the apt cache did change'
+ assert:
+ that:
+ - 'cache_before.stat.mtime != cache_after.stat.mtime'
+
+# When installing a repo with the spec, the key is *NOT* added
+- name: 'ensure ppa key is absent (expect: pass)'
+ apt_key: id='{{test_ppa_key}}' state=absent
+
#
# TEARDOWN
#
diff --git a/test/integration/roles/test_copy/tasks/main.yml b/test/integration/roles/test_copy/tasks/main.yml
index d509093730e..8153ba1508a 100644
--- a/test/integration/roles/test_copy/tasks/main.yml
+++ b/test/integration/roles/test_copy/tasks/main.yml
@@ -117,6 +117,10 @@
register: recursive_copy_result
- debug: var=recursive_copy_result
+- name: assert that the recursive copy did something
+ assert:
+ that:
+ - "recursive_copy_result|changed"
- name: check that a file in a directory was transferred
stat: path={{output_dir}}/sub/subdir/bar.txt
@@ -150,7 +154,7 @@
assert:
that:
- "{{item.stat.mode}} == 0700"
- with_items: dir_stats.results
+ with_items: "{{dir_stats.results}}"
# errors on this aren't presently ignored so this test is commented out. But it would be nice to fix.
diff --git a/test/integration/roles/test_cs_cluster/meta/main.yml b/test/integration/roles/test_cs_cluster/meta/main.yml
new file mode 100644
index 00000000000..03e38bd4f7a
--- /dev/null
+++ b/test/integration/roles/test_cs_cluster/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - test_cs_common
diff --git a/test/integration/roles/test_cs_cluster/tasks/main.yml b/test/integration/roles/test_cs_cluster/tasks/main.yml
new file mode 100644
index 00000000000..bfaa09805d5
--- /dev/null
+++ b/test/integration/roles/test_cs_cluster/tasks/main.yml
@@ -0,0 +1,211 @@
+---
+- name: setup cluster is absent
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ state: absent
+ register: cluster
+- name: verify setup cluster is absent
+ assert:
+ that:
+ - cluster|success
+
+- name: setup zone is present
+ cs_zone:
+ name: "{{ cs_resource_prefix }}-zone"
+ dns1: 8.8.8.8
+ dns2: 8.8.4.4
+ network_type: basic
+ register: zone
+- name: verify setup zone is present
+ assert:
+ that:
+ - zone|success
+
+- name: setup pod is preset
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ start_ip: 10.100.10.101
+ gateway: 10.100.10.1
+ netmask: 255.255.255.0
+ register: pod
+- name: verify setup pod is preset
+ assert:
+ that:
+ - pod|success
+
+- name: test fail if missing name
+ cs_cluster:
+ register: cluster
+ ignore_errors: true
+- name: verify results of fail if missing name
+ assert:
+ that:
+ - cluster|failed
+ - "cluster.msg == 'missing required arguments: name'"
+
+- name: test create cluster
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ zone: "{{ cs_resource_prefix }}-zone"
+ hypervisor: simulator
+ cluster_type: CloudManaged
+ register: cluster_origin
+ tags: disable
+- name: verify test create cluster
+ assert:
+ that:
+ - cluster_origin|changed
+ - cluster_origin.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster_origin.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster_origin.allocation_state == "Enabled"
+ - cluster_origin.hypervisor == "Simulator"
+ - cluster_origin.cluster_type == "CloudManaged"
+
+- name: test create cluster idempotence
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-Cluster"
+ zone: "{{ cs_resource_prefix }}-Zone"
+ hypervisor: Simulator
+ cluster_type: CloudManaged
+ register: cluster
+- name: verify test create cluster idempotence
+ assert:
+ that:
+ - cluster.id == cluster_origin.id
+ - not cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Enabled"
+ - cluster_origin.hypervisor == "Simulator"
+ - cluster.cluster_type == "CloudManaged"
+
+- name: test update cluster
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ zone: "{{ cs_resource_prefix }}-zone"
+ hypervisor: simulator
+ cluster_type: ExternalManaged
+ register: cluster
+- name: verify test update cluster
+ assert:
+ that:
+ - cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Enabled"
+ - cluster.hypervisor == "Simulator"
+ - cluster.cluster_type == "ExternalManaged"
+ - cluster.id == cluster_origin.id
+
+- name: test update cluster idempotence
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ zone: "{{ cs_resource_prefix }}-zone"
+ hypervisor: simulator
+ cluster_type: ExternalManaged
+ register: cluster
+- name: verify test update cluster idempotence
+ assert:
+ that:
+ - not cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Enabled"
+ - cluster.hypervisor == "Simulator"
+ - cluster.cluster_type == "ExternalManaged"
+ - cluster.id == cluster_origin.id
+
+- name: test disable cluster
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ state: disabled
+ register: cluster
+ tags: disable
+- name: verify test disable cluster
+ assert:
+ that:
+ - cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Disabled"
+ - cluster.hypervisor == "Simulator"
+ - cluster.cluster_type == "ExternalManaged"
+ - cluster.id == cluster_origin.id
+ tags: disable
+
+- name: test disable cluster idempotence
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ state: disabled
+ register: cluster
+ tags: disable
+- name: verify test disable cluster idempotence
+ assert:
+ that:
+ - not cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Disabled"
+ - cluster.hypervisor == "Simulator"
+ - cluster.cluster_type == "ExternalManaged"
+ tags: disable
+
+- name: test enable cluster
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ state: enabled
+ register: cluster
+- name: verify test enable cluster
+ assert:
+ that:
+ - cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Enabled"
+ - cluster.hypervisor == "Simulator"
+ - cluster.cluster_type == "ExternalManaged"
+ - cluster.id == cluster_origin.id
+
+- name: test enable cluster idempotence
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ state: enabled
+ register: cluster
+- name: verify test enable cluster idempotence
+ assert:
+ that:
+ - not cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Enabled"
+ - cluster.hypervisor == "Simulator"
+ - cluster.cluster_type == "ExternalManaged"
+ - cluster.id == cluster_origin.id
+
+- name: test remove cluster
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: absent
+ register: cluster
+- name: verify test remove cluster
+ assert:
+ that:
+ - cluster.id == cluster_origin.id
+ - cluster|changed
+ - cluster.name == "{{ cs_resource_prefix }}-cluster"
+ - cluster.zone == "{{ cs_resource_prefix }}-zone"
+ - cluster.allocation_state == "Enabled"
+ - cluster_origin.hypervisor == "Simulator"
+
+- name: test remove cluster idempotence
+ cs_cluster:
+ name: "{{ cs_resource_prefix }}-cluster"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: absent
+ register: cluster
+- name: verify test remove cluster idempotence
+ assert:
+ that:
+ - not cluster|changed
diff --git a/test/integration/roles/test_cs_configuration/defaults/main.yml b/test/integration/roles/test_cs_configuration/defaults/main.yml
new file mode 100644
index 00000000000..2c68b5099aa
--- /dev/null
+++ b/test/integration/roles/test_cs_configuration/defaults/main.yml
@@ -0,0 +1,5 @@
+---
+test_cs_configuration_storage: PS0
+test_cs_configuration_cluster: C0
+test_cs_configuration_account: admin
+test_cs_configuration_zone: Sandbox-simulator
diff --git a/test/integration/roles/test_cs_configuration/meta/main.yml b/test/integration/roles/test_cs_configuration/meta/main.yml
new file mode 100644
index 00000000000..03e38bd4f7a
--- /dev/null
+++ b/test/integration/roles/test_cs_configuration/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - test_cs_common
diff --git a/test/integration/roles/test_cs_configuration/tasks/account.yml b/test/integration/roles/test_cs_configuration/tasks/account.yml
new file mode 100644
index 00000000000..853fbf81a3a
--- /dev/null
+++ b/test/integration/roles/test_cs_configuration/tasks/account.yml
@@ -0,0 +1,59 @@
+---
+- name: test configuration account
+ cs_configuration:
+ name: allow.public.user.templates
+ account: "{{ test_cs_configuration_account }}"
+ value: true
+ register: config
+- name: verify test configuration storage
+ assert:
+ that:
+ - config|success
+
+- name: test update configuration account
+ cs_configuration:
+ name: allow.public.user.templates
+ account: "{{ test_cs_configuration_account }}"
+ value: false
+ register: config
+- name: verify update configuration account
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "false"
+ - config.name == "allow.public.user.templates"
+ - config.scope == "account"
+ - config.account == "{{ test_cs_configuration_account }}"
+
+- name: test update configuration account idempotence
+ cs_configuration:
+ name: allow.public.user.templates
+ account: "{{ test_cs_configuration_account }}"
+ value: false
+ register: config
+- name: verify update configuration account idempotence
+ assert:
+ that:
+ - config|success
+ - not config|changed
+ - config.value == "false"
+ - config.name == "allow.public.user.templates"
+ - config.scope == "account"
+ - config.account == "{{ test_cs_configuration_account }}"
+
+- name: test reset configuration account
+ cs_configuration:
+ name: allow.public.user.templates
+ account: "{{ test_cs_configuration_account }}"
+ value: true
+ register: config
+- name: verify update configuration account
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "true"
+ - config.name == "allow.public.user.templates"
+ - config.scope == "account"
+ - config.account == "{{ test_cs_configuration_account }}"
diff --git a/test/integration/roles/test_cs_configuration/tasks/cluster.yml b/test/integration/roles/test_cs_configuration/tasks/cluster.yml
new file mode 100644
index 00000000000..c3328e41d8f
--- /dev/null
+++ b/test/integration/roles/test_cs_configuration/tasks/cluster.yml
@@ -0,0 +1,59 @@
+---
+- name: test configuration cluster
+ cs_configuration:
+ name: cpu.overprovisioning.factor
+ cluster: "{{ test_cs_configuration_cluster }}"
+ value: 1.0
+ register: config
+- name: verify test configuration cluster
+ assert:
+ that:
+ - config|success
+
+- name: test update configuration cluster
+ cs_configuration:
+ name: cpu.overprovisioning.factor
+ cluster: "{{ test_cs_configuration_cluster }}"
+ value: 2.0
+ register: config
+- name: verify update configuration cluster
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "2.0"
+ - config.name == "cpu.overprovisioning.factor"
+ - config.scope == "cluster"
+ - config.cluster == "{{ test_cs_configuration_cluster }}"
+
+- name: test update configuration cluster idempotence
+ cs_configuration:
+ name: cpu.overprovisioning.factor
+ cluster: "{{ test_cs_configuration_cluster }}"
+ value: 2.0
+ register: config
+- name: verify update configuration cluster idempotence
+ assert:
+ that:
+ - config|success
+ - not config|changed
+ - config.value == "2.0"
+ - config.name == "cpu.overprovisioning.factor"
+ - config.scope == "cluster"
+ - config.cluster == "{{ test_cs_configuration_cluster }}"
+
+- name: test reset configuration cluster
+ cs_configuration:
+ name: cpu.overprovisioning.factor
+ cluster: "{{ test_cs_configuration_cluster }}"
+ value: 1.0
+ register: config
+- name: verify reset configuration cluster
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "1.0"
+ - config.name == "cpu.overprovisioning.factor"
+ - config.scope == "cluster"
+ - config.cluster == "{{ test_cs_configuration_cluster }}"
diff --git a/test/integration/roles/test_cs_configuration/tasks/main.yml b/test/integration/roles/test_cs_configuration/tasks/main.yml
new file mode 100644
index 00000000000..5fdba116809
--- /dev/null
+++ b/test/integration/roles/test_cs_configuration/tasks/main.yml
@@ -0,0 +1,162 @@
+---
+- name: test fail if missing name
+ cs_configuration:
+ register: config
+ ignore_errors: true
+- name: verify results of fail if missing arguments
+ assert:
+ that:
+ - config|failed
+ - "config.msg == 'missing required arguments: value,name'"
+
+- name: test configuration
+ cs_configuration:
+ name: network.loadbalancer.haproxy.stats.visibility
+ value: global
+ register: config
+- name: verify test configuration
+ assert:
+ that:
+ - config|success
+
+- name: test update configuration string
+ cs_configuration:
+ name: network.loadbalancer.haproxy.stats.visibility
+ value: all
+ register: config
+- name: verify test update configuration string
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "all"
+ - config.name == "network.loadbalancer.haproxy.stats.visibility"
+
+- name: test update configuration string idempotence
+ cs_configuration:
+ name: network.loadbalancer.haproxy.stats.visibility
+ value: all
+ register: config
+- name: verify test update configuration string idempotence
+ assert:
+ that:
+ - config|success
+ - not config|changed
+ - config.value == "all"
+ - config.name == "network.loadbalancer.haproxy.stats.visibility"
+
+- name: test reset configuration string
+ cs_configuration:
+ name: network.loadbalancer.haproxy.stats.visibility
+ value: global
+ register: config
+- name: verify test reset configuration string
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "global"
+ - config.name == "network.loadbalancer.haproxy.stats.visibility"
+
+- name: test configuration
+ cs_configuration:
+ name: vmware.recycle.hung.wokervm
+ value: false
+ register: config
+- name: verify test configuration
+ assert:
+ that:
+ - config|success
+
+- name: test update configuration bool
+ cs_configuration:
+ name: vmware.recycle.hung.wokervm
+ value: true
+ register: config
+- name: verify test update configuration bool
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "true"
+ - config.name == "vmware.recycle.hung.wokervm"
+
+- name: test update configuration bool idempotence
+ cs_configuration:
+ name: vmware.recycle.hung.wokervm
+ value: true
+ register: config
+- name: verify test update configuration bool idempotence
+ assert:
+ that:
+ - config|success
+ - not config|changed
+ - config.value == "true"
+ - config.name == "vmware.recycle.hung.wokervm"
+
+- name: test reset configuration bool
+ cs_configuration:
+ name: vmware.recycle.hung.wokervm
+ value: false
+ register: config
+- name: verify test reset configuration bool
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "false"
+ - config.name == "vmware.recycle.hung.wokervm"
+
+- name: test configuration
+ cs_configuration:
+ name: agent.load.threshold
+ value: 0.7
+ register: config
+- name: verify test configuration
+ assert:
+ that:
+ - config|success
+
+- name: test update configuration float
+ cs_configuration:
+ name: agent.load.threshold
+ value: 0.81
+ register: config
+- name: verify update configuration float
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "0.81"
+ - config.name == "agent.load.threshold"
+
+- name: test update configuration float idempotence
+ cs_configuration:
+ name: agent.load.threshold
+ value: 0.81
+ register: config
+- name: verify update configuration float idempotence
+ assert:
+ that:
+ - config|success
+ - not config|changed
+ - config.value == "0.81"
+ - config.name == "agent.load.threshold"
+
+- name: reset configuration float
+ cs_configuration:
+ name: agent.load.threshold
+ value: 0.7
+ register: config
+- name: verify reset configuration float
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "0.7"
+ - config.name == "agent.load.threshold"
+
+- include: storage.yml
+- include: account.yml
+- include: zone.yml
+- include: cluster.yml
diff --git a/test/integration/roles/test_cs_configuration/tasks/storage.yml b/test/integration/roles/test_cs_configuration/tasks/storage.yml
new file mode 100644
index 00000000000..8201bae0572
--- /dev/null
+++ b/test/integration/roles/test_cs_configuration/tasks/storage.yml
@@ -0,0 +1,59 @@
+---
+- name: test configuration storage
+ cs_configuration:
+ name: storage.overprovisioning.factor
+ storage: "{{ test_cs_configuration_storage }}"
+ value: 2.0
+ register: config
+- name: verify test configuration storage
+ assert:
+ that:
+ - config|success
+
+- name: test update configuration storage
+ cs_configuration:
+ name: storage.overprovisioning.factor
+ storage: "{{ test_cs_configuration_storage }}"
+ value: 3.0
+ register: config
+- name: verify update configuration storage
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "3.0"
+ - config.name == "storage.overprovisioning.factor"
+ - config.scope == "storagepool"
+ - config.storage == "{{ test_cs_configuration_storage }}"
+
+- name: test update configuration storage idempotence
+ cs_configuration:
+ name: storage.overprovisioning.factor
+ storage: "{{ test_cs_configuration_storage }}"
+ value: 3.0
+ register: config
+- name: verify update configuration storage idempotence
+ assert:
+ that:
+ - config|success
+ - not config|changed
+ - config.value == "3.0"
+ - config.name == "storage.overprovisioning.factor"
+ - config.scope == "storagepool"
+ - config.storage == "{{ test_cs_configuration_storage }}"
+
+- name: test reset configuration storage
+ cs_configuration:
+ name: storage.overprovisioning.factor
+ storage: "{{ test_cs_configuration_storage }}"
+ value: 2.0
+ register: config
+- name: verify reset configuration storage
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "2.0"
+ - config.name == "storage.overprovisioning.factor"
+ - config.scope == "storagepool"
+ - config.storage == "{{ test_cs_configuration_storage }}"
diff --git a/test/integration/roles/test_cs_configuration/tasks/zone.yml b/test/integration/roles/test_cs_configuration/tasks/zone.yml
new file mode 100644
index 00000000000..423f885c7c0
--- /dev/null
+++ b/test/integration/roles/test_cs_configuration/tasks/zone.yml
@@ -0,0 +1,59 @@
+---
+- name: test configuration zone
+ cs_configuration:
+ name: use.external.dns
+ zone: "{{ test_cs_configuration_zone }}"
+ value: false
+ register: config
+- name: verify test configuration zone
+ assert:
+ that:
+ - config|success
+
+- name: test update configuration zone
+ cs_configuration:
+ name: use.external.dns
+ zone: "{{ test_cs_configuration_zone }}"
+ value: true
+ register: config
+- name: verify update configuration zone
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "true"
+ - config.name == "use.external.dns"
+ - config.scope == "zone"
+ - config.zone == "{{ test_cs_configuration_zone }}"
+
+- name: test update configuration zone idempotence
+ cs_configuration:
+ name: use.external.dns
+ zone: "{{ test_cs_configuration_zone }}"
+ value: true
+ register: config
+- name: verify update configuration zone idempotence
+ assert:
+ that:
+ - config|success
+ - not config|changed
+ - config.value == "true"
+ - config.name == "use.external.dns"
+ - config.scope == "zone"
+ - config.zone == "{{ test_cs_configuration_zone }}"
+
+- name: test reset configuration zone
+ cs_configuration:
+ name: use.external.dns
+ zone: "{{ test_cs_configuration_zone }}"
+ value: false
+ register: config
+- name: verify reset configuration zone
+ assert:
+ that:
+ - config|success
+ - config|changed
+ - config.value == "false"
+ - config.name == "use.external.dns"
+ - config.scope == "zone"
+ - config.zone == "{{ test_cs_configuration_zone }}"
diff --git a/test/integration/roles/test_cs_instance_facts/defaults/main.yml b/test/integration/roles/test_cs_instance_facts/defaults/main.yml
new file mode 100644
index 00000000000..e393e60f4f0
--- /dev/null
+++ b/test/integration/roles/test_cs_instance_facts/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+test_cs_instance_template: CentOS 5.3(64-bit) no GUI (Simulator)
+test_cs_instance_offering_1: Small Instance
diff --git a/test/integration/roles/test_cs_instance_facts/meta/main.yml b/test/integration/roles/test_cs_instance_facts/meta/main.yml
new file mode 100644
index 00000000000..03e38bd4f7a
--- /dev/null
+++ b/test/integration/roles/test_cs_instance_facts/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - test_cs_common
diff --git a/test/integration/roles/test_cs_instance_facts/tasks/main.yml b/test/integration/roles/test_cs_instance_facts/tasks/main.yml
new file mode 100644
index 00000000000..af35712aa5c
--- /dev/null
+++ b/test/integration/roles/test_cs_instance_facts/tasks/main.yml
@@ -0,0 +1,55 @@
+---
+- name: setup ssh key
+ cs_sshkeypair: name={{ cs_resource_prefix }}-sshkey
+ register: sshkey
+- name: verify setup ssh key
+ assert:
+ that:
+ - sshkey|success
+
+- name: setup affinity group
+ cs_affinitygroup: name={{ cs_resource_prefix }}-ag
+ register: ag
+- name: verify setup affinity group
+ assert:
+ that:
+ - ag|success
+
+- name: setup security group
+ cs_securitygroup: name={{ cs_resource_prefix }}-sg
+ register: sg
+- name: verify setup security group
+ assert:
+ that:
+ - sg|success
+
+- name: setup instance
+ cs_instance:
+ name: "{{ cs_resource_prefix }}-vm"
+ template: "{{ test_cs_instance_template }}"
+ service_offering: "{{ test_cs_instance_offering_1 }}"
+ affinity_group: "{{ cs_resource_prefix }}-ag"
+ security_group: "{{ cs_resource_prefix }}-sg"
+ ssh_key: "{{ cs_resource_prefix }}-sshkey"
+ tags: []
+ register: instance
+- name: verify create instance
+ assert:
+ that:
+ - instance|success
+
+- name: test instance facts
+ cs_instance_facts:
+ name: "{{ cs_resource_prefix }}-vm"
+ register: instance_facts
+- name: verify test instance facts
+ assert:
+ that:
+ - instance_facts|success
+ - not instance_facts|changed
+ - cloudstack_instance.id == instance.id
+ - cloudstack_instance.domain == instance.domain
+ - cloudstack_instance.account == instance.account
+ - cloudstack_instance.zone == instance.zone
+ - cloudstack_instance.name == instance.name
+ - cloudstack_instance.service_offering == instance.service_offering
diff --git a/test/integration/roles/test_cs_pod/meta/main.yml b/test/integration/roles/test_cs_pod/meta/main.yml
new file mode 100644
index 00000000000..03e38bd4f7a
--- /dev/null
+++ b/test/integration/roles/test_cs_pod/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - test_cs_common
diff --git a/test/integration/roles/test_cs_pod/tasks/main.yml b/test/integration/roles/test_cs_pod/tasks/main.yml
new file mode 100644
index 00000000000..6f84eb783b6
--- /dev/null
+++ b/test/integration/roles/test_cs_pod/tasks/main.yml
@@ -0,0 +1,210 @@
+---
+- name: setup pod is absent
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ state: absent
+ register: pod
+- name: verify setup pod is absent
+ assert:
+ that:
+ - pod|success
+
+- name: setup zone is present
+ cs_zone:
+ name: "{{ cs_resource_prefix }}-zone"
+ dns1: 8.8.8.8
+ dns2: 8.8.4.4
+ network_type: basic
+ register: zone
+- name: verify setup zone is present
+ assert:
+ that:
+ - zone|success
+
+- name: test fail if missing name
+ cs_pod:
+ register: pod
+ ignore_errors: true
+- name: verify results of fail if missing name
+ assert:
+ that:
+ - pod|failed
+ - "pod.msg == 'missing required arguments: name'"
+
+- name: test create pod
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ start_ip: 10.100.10.101
+ gateway: 10.100.10.1
+ netmask: 255.255.255.0
+ register: pod_origin
+- name: verify test create pod
+ assert:
+ that:
+ - pod_origin|changed
+ - pod_origin.allocation_state == "Enabled"
+ - pod_origin.start_ip == "10.100.10.101"
+ - pod_origin.end_ip == "10.100.10.254"
+ - pod_origin.gateway == "10.100.10.1"
+ - pod_origin.netmask == "255.255.255.0"
+ - pod_origin.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test create pod idempotence
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ start_ip: 10.100.10.101
+ gateway: 10.100.10.1
+ netmask: 255.255.255.0
+ register: pod
+- name: verify test create pod idempotence
+ assert:
+ that:
+ - not pod|changed
+ - pod.allocation_state == "Enabled"
+ - pod.start_ip == "10.100.10.101"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test update pod
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ start_ip: 10.100.10.102
+ gateway: 10.100.10.1
+ netmask: 255.255.255.0
+ register: pod
+- name: verify test update pod
+ assert:
+ that:
+ - pod|changed
+ - pod.allocation_state == "Enabled"
+ - pod.start_ip == "10.100.10.102"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test update pod idempotence
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ start_ip: 10.100.10.102
+ gateway: 10.100.10.1
+ netmask: 255.255.255.0
+ register: pod
+- name: verify test update pod idempotence
+ assert:
+ that:
+ - not pod|changed
+ - pod.allocation_state == "Enabled"
+ - pod.start_ip == "10.100.10.102"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test disable pod
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: disabled
+ register: pod
+- name: verify test enable pod
+ assert:
+ that:
+ - pod|changed
+ - pod.allocation_state == "Disabled"
+ - pod.id == pod_origin.id
+ - pod.start_ip == "10.100.10.102"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test disable pod idempotence
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: disabled
+ register: pod
+- name: verify test enable pod idempotence
+ assert:
+ that:
+ - not pod|changed
+ - pod.allocation_state == "Disabled"
+ - pod.id == pod_origin.id
+ - pod.start_ip == "10.100.10.102"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test enable pod
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: enabled
+ register: pod
+- name: verify test disable pod
+ assert:
+ that:
+ - pod|changed
+ - pod.allocation_state == "Enabled"
+ - pod.id == pod_origin.id
+ - pod.start_ip == "10.100.10.102"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+
+- name: test enable pod idempotence
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: enabled
+ register: pod
+- name: verify test enabled pod idempotence
+ assert:
+ that:
+ - not pod|changed
+ - pod.allocation_state == "Enabled"
+ - pod.id == pod_origin.id
+ - pod.start_ip == "10.100.10.102"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test absent pod
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: absent
+ register: pod
+- name: verify test create pod
+ assert:
+ that:
+ - pod|changed
+ - pod.id == pod_origin.id
+ - pod.allocation_state == "Enabled"
+ - pod.start_ip == "10.100.10.102"
+ - pod.end_ip == "10.100.10.254"
+ - pod.gateway == "10.100.10.1"
+ - pod.netmask == "255.255.255.0"
+ - pod.zone == "{{ cs_resource_prefix }}-zone"
+
+- name: test absent pod idempotence
+ cs_pod:
+ name: "{{ cs_resource_prefix }}-pod"
+ zone: "{{ cs_resource_prefix }}-zone"
+ state: absent
+ register: pod
+- name: verify test absent pod idempotence
+ assert:
+ that:
+ - not pod|changed
diff --git a/test/integration/roles/test_cs_resourcelimit/meta/main.yml b/test/integration/roles/test_cs_resourcelimit/meta/main.yml
new file mode 100644
index 00000000000..03e38bd4f7a
--- /dev/null
+++ b/test/integration/roles/test_cs_resourcelimit/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - test_cs_common
diff --git a/test/integration/roles/test_cs_resourcelimit/tasks/cpu.yml b/test/integration/roles/test_cs_resourcelimit/tasks/cpu.yml
new file mode 100644
index 00000000000..5faa6a9233c
--- /dev/null
+++ b/test/integration/roles/test_cs_resourcelimit/tasks/cpu.yml
@@ -0,0 +1,76 @@
+---
+- name: setup cpu limits account
+ cs_resourcelimit:
+ type: cpu
+ limit: 20
+ account: "{{ cs_resource_prefix }}_user"
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify setup cpu limits account
+ assert:
+ that:
+ - rl|success
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.account == "{{ cs_resource_prefix }}_user"
+ - rl.limit == 20
+ - rl.resource_type == "cpu"
+
+- name: set cpu limits for domain
+ cs_resourcelimit:
+ type: cpu
+ limit: 12
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set cpu limits for domain
+ assert:
+ that:
+ - rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.limit == 12
+ - rl.resource_type == "cpu"
+
+- name: set cpu limits for domain idempotence
+ cs_resourcelimit:
+ type: cpu
+ limit: 12
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set cpu limits for domain
+ assert:
+ that:
+ - not rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.limit == 12
+ - rl.resource_type == "cpu"
+
+- name: set cpu limits for account
+ cs_resourcelimit:
+ type: cpu
+ limit: 10
+ account: "{{ cs_resource_prefix }}_user"
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set cpu limits for account
+ assert:
+ that:
+ - rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.account == "{{ cs_resource_prefix }}_user"
+ - rl.limit == 10
+ - rl.resource_type == "cpu"
+
+- name: set cpu limits for account idempotence
+ cs_resourcelimit:
+ type: cpu
+ limit: 10
+ account: "{{ cs_resource_prefix }}_user"
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set cpu limits for account idempotence
+ assert:
+ that:
+ - not rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.account == "{{ cs_resource_prefix }}_user"
+ - rl.limit == 10
+ - rl.resource_type == "cpu"
diff --git a/test/integration/roles/test_cs_resourcelimit/tasks/instance.yml b/test/integration/roles/test_cs_resourcelimit/tasks/instance.yml
new file mode 100644
index 00000000000..9fea9a3545b
--- /dev/null
+++ b/test/integration/roles/test_cs_resourcelimit/tasks/instance.yml
@@ -0,0 +1,76 @@
+---
+- name: setup instance limits account
+ cs_resourcelimit:
+ type: instance
+ limit: 20
+ account: "{{ cs_resource_prefix }}_user"
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify setup instance limits account
+ assert:
+ that:
+ - rl|success
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.account == "{{ cs_resource_prefix }}_user"
+ - rl.limit == 20
+ - rl.resource_type == "instance"
+
+- name: set instance limits for domain
+ cs_resourcelimit:
+ type: instance
+ limit: 12
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set instance limits for domain
+ assert:
+ that:
+ - rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.limit == 12
+ - rl.resource_type == "instance"
+
+- name: set instance limits for domain idempotence
+ cs_resourcelimit:
+ type: instance
+ limit: 12
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set instance limits for domain
+ assert:
+ that:
+ - not rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.limit == 12
+ - rl.resource_type == "instance"
+
+- name: set instance limits for account
+ cs_resourcelimit:
+ type: instance
+ limit: 10
+ account: "{{ cs_resource_prefix }}_user"
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set instance limits for account
+ assert:
+ that:
+ - rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.account == "{{ cs_resource_prefix }}_user"
+ - rl.limit == 10
+ - rl.resource_type == "instance"
+
+- name: set instance limits for account idempotence
+ cs_resourcelimit:
+ type: instance
+ limit: 10
+ account: "{{ cs_resource_prefix }}_user"
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify set instance limits for account idempotence
+ assert:
+ that:
+ - not rl|changed
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.account == "{{ cs_resource_prefix }}_user"
+ - rl.limit == 10
+ - rl.resource_type == "instance"
diff --git a/test/integration/roles/test_cs_resourcelimit/tasks/main.yml b/test/integration/roles/test_cs_resourcelimit/tasks/main.yml
new file mode 100644
index 00000000000..f662bb939a8
--- /dev/null
+++ b/test/integration/roles/test_cs_resourcelimit/tasks/main.yml
@@ -0,0 +1,61 @@
+---
+- name: setup domain
+ cs_domain: path={{ cs_resource_prefix }}-domain
+ register: dom
+- name: verify setup domain
+ assert:
+ that:
+ - dom|success
+
+- name: setup account
+ cs_account:
+ name: "{{ cs_resource_prefix }}_user"
+ username: "{{ cs_resource_prefix }}_username"
+ password: "{{ cs_resource_prefix }}_password"
+ last_name: "{{ cs_resource_prefix }}_last_name"
+ first_name: "{{ cs_resource_prefix }}_first_name"
+ email: "{{ cs_resource_prefix }}@example.com"
+ network_domain: "{{ cs_resource_prefix }}-local"
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: acc
+- name: verify setup account
+ assert:
+ that:
+ - acc|success
+
+- name: test failed unkonwn type
+ cs_resourcelimit:
+ type: unkonwn
+ limit: 20
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+ ignore_errors: yes
+- name: verify test failed unkonwn type
+ assert:
+ that:
+ - rl|failed
+
+- name: test failed missing type
+ cs_resourcelimit:
+ register: rl
+ ignore_errors: yes
+- name: verify test failed missing type
+ assert:
+ that:
+ - rl|failed
+
+- name: setup resource limits domain
+ cs_resourcelimit:
+ type: instance
+ limit: 20
+ domain: "{{ cs_resource_prefix }}-domain"
+ register: rl
+- name: verify setup resource limits domain
+ assert:
+ that:
+ - rl|success
+ - rl.domain == "{{ cs_resource_prefix }}-domain"
+ - rl.limit == 20
+
+- include: instance.yml
+- include: cpu.yml
diff --git a/test/integration/roles/test_cs_volume/defaults/main.yml b/test/integration/roles/test_cs_volume/defaults/main.yml
new file mode 100644
index 00000000000..311a99bbe82
--- /dev/null
+++ b/test/integration/roles/test_cs_volume/defaults/main.yml
@@ -0,0 +1,6 @@
+---
+test_cs_instance_1: "{{ cs_resource_prefix }}-vm1"
+test_cs_instance_2: "{{ cs_resource_prefix }}-vm2"
+test_cs_instance_template: CentOS 5.3(64-bit) no GUI (Simulator)
+test_cs_instance_offering_1: Small Instance
+test_cs_disk_offering_1: Custom
diff --git a/test/integration/roles/test_cs_volume/meta/main.yml b/test/integration/roles/test_cs_volume/meta/main.yml
new file mode 100644
index 00000000000..03e38bd4f7a
--- /dev/null
+++ b/test/integration/roles/test_cs_volume/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - test_cs_common
diff --git a/test/integration/roles/test_cs_volume/tasks/main.yml b/test/integration/roles/test_cs_volume/tasks/main.yml
new file mode 100644
index 00000000000..ae57039cee8
--- /dev/null
+++ b/test/integration/roles/test_cs_volume/tasks/main.yml
@@ -0,0 +1,215 @@
+---
+- name: setup
+ cs_volume: name={{ cs_resource_prefix }}_vol state=absent
+ register: vol
+- name: verify setup
+ assert:
+ that:
+ - vol|success
+
+- name: setup instance 1
+ cs_instance:
+ name: "{{ test_cs_instance_1 }}"
+ template: "{{ test_cs_instance_template }}"
+ service_offering: "{{ test_cs_instance_offering_1 }}"
+ register: instance
+- name: verify create instance
+ assert:
+ that:
+ - instance|success
+
+- name: setup instance 2
+ cs_instance:
+ name: "{{ test_cs_instance_2 }}"
+ template: "{{ test_cs_instance_template }}"
+ service_offering: "{{ test_cs_instance_offering_1 }}"
+ register: instance
+- name: verify create instance
+ assert:
+ that:
+ - instance|success
+
+- name: test fail if missing name
+ action: cs_volume
+ register: vol
+ ignore_errors: true
+- name: verify results of fail if missing name
+ assert:
+ that:
+ - vol|failed
+ - "vol.msg == 'missing required arguments: name'"
+
+- name: test create volume
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ disk_offering: "{{ test_cs_disk_offering_1 }}"
+ size: 20
+ register: vol
+- name: verify results test create volume
+ assert:
+ that:
+ - vol|changed
+ - vol.size == 20 * 1024 ** 3
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+
+- name: test create volume idempotence
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ disk_offering: "{{ test_cs_disk_offering_1 }}"
+ size: 20
+ register: vol
+- name: verify results test create volume idempotence
+ assert:
+ that:
+ - not vol|changed
+ - vol.size == 20 * 1024 ** 3
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+
+- name: test shrink volume
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ disk_offering: "{{ test_cs_disk_offering_1 }}"
+ size: 10
+ shrink_ok: yes
+ register: vol
+- name: verify results test create volume
+ assert:
+ that:
+ - vol|changed
+ - vol.size == 10 * 1024 ** 3
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+
+- name: test shrink volume idempotence
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ disk_offering: "{{ test_cs_disk_offering_1 }}"
+ size: 10
+ shrink_ok: yes
+ register: vol
+- name: verify results test create volume
+ assert:
+ that:
+ - not vol|changed
+ - vol.size == 10 * 1024 ** 3
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+
+- name: test attach volume
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ vm: "{{ test_cs_instance_1 }}"
+ state: attached
+ register: vol
+- name: verify results test attach volume
+ assert:
+ that:
+ - vol|changed
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+ - vol.vm == "{{ test_cs_instance_1 }}"
+ - vol.attached is defined
+
+- name: test attach volume idempotence
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ vm: "{{ test_cs_instance_1 }}"
+ state: attached
+ register: vol
+- name: verify results test attach volume idempotence
+ assert:
+ that:
+ - not vol|changed
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+ - vol.vm == "{{ test_cs_instance_1 }}"
+ - vol.attached is defined
+
+- name: test attach attached volume to another vm
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ vm: "{{ test_cs_instance_2 }}"
+ state: attached
+ register: vol
+- name: verify results test attach attached volume to another vm
+ assert:
+ that:
+ - vol|changed
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+ - vol.vm == "{{ test_cs_instance_2 }}"
+ - vol.attached is defined
+
+- name: test attach attached volume to another vm idempotence
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ vm: "{{ test_cs_instance_2 }}"
+ state: attached
+ register: vol
+- name: verify results test attach attached volume to another vm idempotence
+ assert:
+ that:
+ - not vol|changed
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+ - vol.vm == "{{ test_cs_instance_2 }}"
+ - vol.attached is defined
+
+- name: test detach volume
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ state: detached
+ register: vol
+- name: verify results test detach volume
+ assert:
+ that:
+ - vol|changed
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+ - vol.attached is undefined
+
+- name: test detach volume idempotence
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ state: detached
+ register: vol
+- name: verify results test detach volume idempotence
+ assert:
+ that:
+ - not vol|changed
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+ - vol.attached is undefined
+
+- name: test delete volume
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ state: absent
+ register: vol
+- name: verify results test create volume
+ assert:
+ that:
+ - vol|changed
+ - vol.name == "{{ cs_resource_prefix }}_vol"
+
+- name: test delete volume idempotence
+ cs_volume:
+ name: "{{ cs_resource_prefix }}_vol"
+ state: absent
+ register: vol
+- name: verify results test delete volume idempotence
+ assert:
+ that:
+ - not vol|changed
+
+- name: cleanup instance 1
+ cs_instance:
+ name: "{{ test_cs_instance_1 }}"
+ state: absent
+ register: instance
+- name: verify create instance
+ assert:
+ that:
+ - instance|success
+
+- name: cleanup instance 2
+ cs_instance:
+ name: "{{ test_cs_instance_2 }}"
+ state: absent
+ register: instance
+- name: verify create instance
+ assert:
+ that:
+ - instance|success
diff --git a/test/integration/roles/test_file/tasks/main.yml b/test/integration/roles/test_file/tasks/main.yml
index 518f91bf744..d5ba22645d8 100644
--- a/test/integration/roles/test_file/tasks/main.yml
+++ b/test/integration/roles/test_file/tasks/main.yml
@@ -248,7 +248,7 @@
that:
- 'item.changed == true'
- 'item.state == "file"'
- with_items: file16_result.results
+ with_items: "{{file16_result.results}}"
- name: try to force the sub-directory to a link
file: src={{output_dir}}/testing dest={{output_dir}}/sub1 state=link force=yes
diff --git a/test/integration/roles/test_filters/tasks/main.yml b/test/integration/roles/test_filters/tasks/main.yml
index a94bb8c6559..6d75c0d81cf 100644
--- a/test/integration/roles/test_filters/tasks/main.yml
+++ b/test/integration/roles/test_filters/tasks/main.yml
@@ -68,3 +68,12 @@
- '"0.10 GB" == 102400000|human_readable(unit="G")'
- '"0.10 Gb" == 102400000|human_readable(isbits=True, unit="G")'
+- name: Container lookups with extract
+ assert:
+ that:
+ - "'x' == [0]|map('extract',['x','y'])|list|first"
+ - "'y' == [1]|map('extract',['x','y'])|list|first"
+ - "42 == ['x']|map('extract',{'x':42,'y':31})|list|first"
+ - "31 == ['x','y']|map('extract',{'x':42,'y':31})|list|last"
+ - "'local' == ['localhost']|map('extract',hostvars,'ansible_connection')|list|first"
+ - "'local' == ['localhost']|map('extract',hostvars,['ansible_connection'])|list|first"
diff --git a/test/integration/roles/test_get_url/tasks/main.yml b/test/integration/roles/test_get_url/tasks/main.yml
index 844a25472c9..61b26f08f61 100644
--- a/test/integration/roles/test_get_url/tasks/main.yml
+++ b/test/integration/roles/test_get_url/tasks/main.yml
@@ -146,6 +146,11 @@
when: "{{ not python_has_ssl_context }}"
# End hacky SNI test section
+- name: Test get_url with redirect
+ get_url:
+ url: 'http://httpbin.org/redirect/6'
+ dest: "{{ output_dir }}/redirect.json"
+
- name: Test that setting file modes work
get_url:
url: 'http://httpbin.org/'
@@ -178,4 +183,4 @@
assert:
that:
- "result.changed == true"
- - "stat_result.stat.mode == '0070'"
\ No newline at end of file
+ - "stat_result.stat.mode == '0070'"
diff --git a/test/integration/roles/test_git/tasks/main.yml b/test/integration/roles/test_git/tasks/main.yml
index 49f5f53bfb8..e2e9e8bf3ca 100644
--- a/test/integration/roles/test_git/tasks/main.yml
+++ b/test/integration/roles/test_git/tasks/main.yml
@@ -88,7 +88,7 @@
- name: remove known_host files
file: state=absent path={{ item }}
- with_items: known_host_files
+ with_items: "{{known_host_files}}"
- name: checkout ssh://git@github.com repo without accept_hostkey (expected fail)
git: repo={{ repo_format2 }} dest={{ checkout_dir }}
diff --git a/test/integration/roles/test_lookups/tasks/main.yml b/test/integration/roles/test_lookups/tasks/main.yml
index 3c5e066ee34..5b179690f11 100644
--- a/test/integration/roles/test_lookups/tasks/main.yml
+++ b/test/integration/roles/test_lookups/tasks/main.yml
@@ -194,3 +194,23 @@
- assert:
that:
- "'www.kennethreitz.org' in web_data"
+
+- name: Test cartesian lookup
+ debug: var={{item}}
+ with_cartesian:
+ - ["A", "B", "C"]
+ - ["1", "2", "3"]
+ register: product
+
+- name: Verify cartesian lookup
+ assert:
+ that:
+ - product.results[0]['item'] == ["A", "1"]
+ - product.results[1]['item'] == ["A", "2"]
+ - product.results[2]['item'] == ["A", "3"]
+ - product.results[3]['item'] == ["B", "1"]
+ - product.results[4]['item'] == ["B", "2"]
+ - product.results[5]['item'] == ["B", "3"]
+ - product.results[6]['item'] == ["C", "1"]
+ - product.results[7]['item'] == ["C", "2"]
+ - product.results[8]['item'] == ["C", "3"]
diff --git a/test/integration/roles/test_mysql_user/tasks/main.yml b/test/integration/roles/test_mysql_user/tasks/main.yml
index 68042e74913..b0754390403 100644
--- a/test/integration/roles/test_mysql_user/tasks/main.yml
+++ b/test/integration/roles/test_mysql_user/tasks/main.yml
@@ -96,7 +96,7 @@
mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password={{ user_password_1 }}
with_nested:
- [ '{{ user_name_1 }}' , '{{ user_name_2 }}']
- - db_names
+ - "{{db_names}}"
- name: show grants access for user1 on multiple database
command: mysql "-e SHOW GRANTS FOR '{{ user_name_1 }}'@'localhost';"
@@ -104,7 +104,7 @@
- name: assert grant access for user1 on multiple database
assert: { that: "'{{ item }}' in result.stdout" }
- with_items: db_names
+ with_items: "{{db_names}}"
- name: show grants access for user2 on multiple database
command: mysql "-e SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost';"
diff --git a/test/integration/roles/test_mysql_variables/tasks/main.yml b/test/integration/roles/test_mysql_variables/tasks/main.yml
index 5344e5b1b48..26fc03c8679 100644
--- a/test/integration/roles/test_mysql_variables/tasks/main.yml
+++ b/test/integration/roles/test_mysql_variables/tasks/main.yml
@@ -195,7 +195,7 @@
# Verify mysql_variable fails with an incorrect login_host parameter
#
- name: query mysql_variable using incorrect login_host
- mysql_variables: variable=wait_timeout login_host=12.0.0.9
+ mysql_variables: variable=wait_timeout login_host=12.0.0.9 connect_timeout=5
register: result
ignore_errors: true
diff --git a/test/integration/roles/test_uri/tasks/main.yml b/test/integration/roles/test_uri/tasks/main.yml
index 4d8f9c7db09..4c06ea1ad7c 100644
--- a/test/integration/roles/test_uri/tasks/main.yml
+++ b/test/integration/roles/test_uri/tasks/main.yml
@@ -69,8 +69,8 @@
- '"json" in item.1'
- item.0.stat.checksum == item.1.content | checksum
with_together:
- - pass_checksum.results
- - pass.results
+ - "{{pass_checksum.results}}"
+ - "{{pass.results}}"
- name: checksum fail_json
@@ -89,8 +89,8 @@
- item.0.stat.checksum == item.1.content | checksum
- '"json" not in item.1'
with_together:
- - fail_checksum.results
- - fail.results
+ - "{{fail_checksum.results}}"
+ - "{{fail.results}}"
- name: test https fetch to a site with mismatched hostname and certificate
uri:
@@ -153,6 +153,28 @@
that:
- 'result.location|default("") == "http://httpbin.org/relative-redirect/1"'
+- name: Check SSL with redirect
+ uri:
+ url: 'https://httpbin.org/redirect/2'
+ register: result
+
+- name: Assert SSL with redirect
+ assert:
+ that:
+ - 'result.url|default("") == "https://httpbin.org/get"'
+
+- name: redirect to bad SSL site
+ uri:
+ url: 'http://wrong.host.badssl.com'
+ register: result
+ ignore_errors: true
+
+- name: Ensure bad SSL site reidrect fails
+ assert:
+ that:
+ - result|failed
+ - '"wrong.host.badssl.com" in result.msg'
+
- name: test basic auth
uri:
url: 'http://httpbin.org/basic-auth/user/passwd'
diff --git a/test/integration/roles/test_win_copy/tasks/main.yml b/test/integration/roles/test_win_copy/tasks/main.yml
index 3d29775894e..ff26117e64d 100644
--- a/test/integration/roles/test_win_copy/tasks/main.yml
+++ b/test/integration/roles/test_win_copy/tasks/main.yml
@@ -183,7 +183,7 @@
# assert:
# that:
# - "{{item.stat.mode}} == 0700"
-# with_items: dir_stats.results
+# with_items: "{{dir_stats.results}}"
# errors on this aren't presently ignored so this test is commented out. But it would be nice to fix.
diff --git a/test/integration/roles/test_win_file/tasks/main.yml b/test/integration/roles/test_win_file/tasks/main.yml
index a8d6e92b3d3..b128715226a 100644
--- a/test/integration/roles/test_win_file/tasks/main.yml
+++ b/test/integration/roles/test_win_file/tasks/main.yml
@@ -183,7 +183,7 @@
that:
- 'item.changed == true'
# - 'item.state == "file"'
- with_items: file16_result.results
+ with_items: "{{file16_result.results}}"
#- name: try to force the sub-directory to a link
# win_file: src={{win_output_dir}}/testing dest={{win_output_dir}}/sub1 state=link force=yes
diff --git a/test/integration/roles/test_win_regmerge/files/settings1.reg b/test/integration/roles/test_win_regmerge/files/settings1.reg
new file mode 100644
index 00000000000..baec75b2af0
Binary files /dev/null and b/test/integration/roles/test_win_regmerge/files/settings1.reg differ
diff --git a/test/integration/roles/test_win_regmerge/files/settings2.reg b/test/integration/roles/test_win_regmerge/files/settings2.reg
new file mode 100644
index 00000000000..fc2612cb8a8
Binary files /dev/null and b/test/integration/roles/test_win_regmerge/files/settings2.reg differ
diff --git a/test/integration/roles/test_win_regmerge/files/settings3.reg b/test/integration/roles/test_win_regmerge/files/settings3.reg
new file mode 100644
index 00000000000..fbe7411c95e
Binary files /dev/null and b/test/integration/roles/test_win_regmerge/files/settings3.reg differ
diff --git a/test/integration/roles/test_win_regmerge/meta/main.yml b/test/integration/roles/test_win_regmerge/meta/main.yml
new file mode 100644
index 00000000000..55200b3fc64
--- /dev/null
+++ b/test/integration/roles/test_win_regmerge/meta/main.yml
@@ -0,0 +1,3 @@
+dependencies:
+ - prepare_win_tests
+
diff --git a/test/integration/roles/test_win_regmerge/tasks/main.yml b/test/integration/roles/test_win_regmerge/tasks/main.yml
new file mode 100644
index 00000000000..6e64c9dd4a7
--- /dev/null
+++ b/test/integration/roles/test_win_regmerge/tasks/main.yml
@@ -0,0 +1,133 @@
+# test code for the win_regmerge module
+# (c) 2014, Michael DeHaan
+
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+# clear the area of the registry we are using for tests
+- name: remove setting
+ win_regedit:
+ key: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp'
+ state: absent
+
+# copy over some registry files to work with
+- name: copy over some registry files to work with
+ win_copy: src={{item}} dest={{win_output_dir}}\\{{item}}
+ with_items:
+ - settings1.reg
+ - settings2.reg
+ - settings3.reg
+
+# test 1 - basic test of changed behaviour
+# merge in REG_SZ
+- name: test 1 merge in a setting
+ win_regmerge:
+ path: "{{win_output_dir}}\\settings1.reg"
+ register: merge11_result
+
+- assert:
+ that:
+ - "merge11_result.changed == true"
+
+# re run the merge
+- name: test 1 merge in the setting again
+ win_regmerge:
+ path: "{{win_output_dir}}\\settings1.reg"
+ register: merge12_result
+
+# without a compare to key, should allways report changed
+- assert:
+ that:
+ - "merge12_result.changed == true"
+# assert changed false
+
+# prune reg key
+- name: test 1 remove setting
+ win_regedit:
+ key: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp'
+ state: absent
+
+#
+# test 2, observe behaviour when compare_to param is set
+#
+- name: test 2 merge in a setting
+ win_regmerge:
+ path: "{{win_output_dir}}\\settings1.reg"
+ compare_to: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp\Moosic\ILikeToMooveIt'
+ register: merge21_result
+
+- assert:
+ that:
+ - "merge21_result.changed == true"
+
+# re run the merge
+- name: test 2 merge in the setting again but with compare_key
+ win_regmerge:
+ path: "{{win_output_dir}}\\settings1.reg"
+ compare_to: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp\Moosic\ILikeToMooveIt'
+ register: merge22_result
+
+# with a compare to key, should now report not changed
+- assert:
+ that:
+ - "merge22_result.changed == false"
+# assert changed false
+
+# prune the contents of the registry from the parent of the compare key downwards
+- name: test 2 clean up remove setting
+ win_regedit:
+ key: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp'
+ state: absent
+
+# test 3 merge in more complex settings
+- name: test 3 merge in a setting
+ win_regmerge:
+ path: "{{win_output_dir}}\\settings3.reg"
+ compare_to: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp\Moo Monitor'
+ register: merge31_result
+
+- assert:
+ that:
+ - "merge31_result.changed == true"
+
+# re run the merge
+- name: test 3 merge in the setting again but with compare_key check
+ win_regmerge:
+ path: "{{win_output_dir}}\\settings3.reg"
+ compare_to: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp\Moo Monitor'
+ register: merge32_result
+
+# with a compare to key, should now report not changed
+- assert:
+ that:
+ - "merge32_result.changed == false"
+# assert changed false
+
+# prune the contents of the registry from the compare key downwards
+- name: test 3 clean up remove setting
+ win_regedit:
+ key: 'HKLM:\SOFTWARE\Wow6432Node\Cow Corp'
+ state: absent
+
+# clean up registry files
+
+- name: clean up registry files
+ win_file: path={{win_output_dir}}\\{{item}} state=absent
+ with_items:
+ - settings1.reg
+ - settings2.reg
+ - settings3.reg
+
+# END OF win_regmerge tests
diff --git a/test/integration/roles/test_win_regmerge/templates/win_line_ending.j2 b/test/integration/roles/test_win_regmerge/templates/win_line_ending.j2
new file mode 100644
index 00000000000..d0cefd76f49
--- /dev/null
+++ b/test/integration/roles/test_win_regmerge/templates/win_line_ending.j2
@@ -0,0 +1,4 @@
+#jinja2: newline_sequence:'\r\n'
+{{ templated_var }}
+{{ templated_var }}
+{{ templated_var }}
diff --git a/test/integration/roles/test_win_regmerge/vars/main.yml b/test/integration/roles/test_win_regmerge/vars/main.yml
new file mode 100644
index 00000000000..1e8f64ccf44
--- /dev/null
+++ b/test/integration/roles/test_win_regmerge/vars/main.yml
@@ -0,0 +1 @@
+templated_var: templated_var_loaded
diff --git a/test/integration/test_blocks/main.yml b/test/integration/test_blocks/main.yml
index cb6fc66600e..d318145ac9a 100644
--- a/test/integration/test_blocks/main.yml
+++ b/test/integration/test_blocks/main.yml
@@ -33,17 +33,17 @@
- name: set block always run flag
set_fact:
block_always_run: true
- - block:
- - meta: noop
- always:
- - name: set nested block always run flag
- set_fact:
- nested_block_always_run: true
- - name: fail in always
- fail:
- - name: tasks flag should not be set after failure in always
- set_fact:
- always_run_after_failure: true
+ #- block:
+ # - meta: noop
+ # always:
+ # - name: set nested block always run flag
+ # set_fact:
+ # nested_block_always_run: true
+ # - name: fail in always
+ # fail:
+ # - name: tasks flag should not be set after failure in always
+ # set_fact:
+ # always_run_after_failure: true
- meta: clear_host_errors
post_tasks:
@@ -52,7 +52,7 @@
- block_tasks_run
- block_rescue_run
- block_always_run
- - nested_block_always_run
+ #- nested_block_always_run
- not tasks_run_after_failure
- not rescue_run_after_failure
- not always_run_after_failure
@@ -84,7 +84,7 @@
include: fail.yml
args:
msg: "failed from rescue"
- - name: tasks flag should not be set after failure in rescue
+ - name: flag should not be set after failure in rescue
set_fact:
rescue_run_after_failure: true
always:
diff --git a/test/integration/test_connection.inventory b/test/integration/test_connection.inventory
new file mode 100644
index 00000000000..261bd7020f8
--- /dev/null
+++ b/test/integration/test_connection.inventory
@@ -0,0 +1,56 @@
+[local]
+local-pipelining ansible_ssh_pipelining=true
+local-no-pipelining ansible_ssh_pipelining=false
+[local:vars]
+ansible_host=localhost
+ansible_connection=local
+
+[chroot]
+chroot-pipelining ansible_ssh_pipelining=true
+chroot-no-pipelining ansible_ssh_pipelining=false
+[chroot:vars]
+ansible_host=/
+ansible_connection=chroot
+
+[docker]
+docker-pipelining ansible_ssh_pipelining=true
+docker-no-pipelining ansible_ssh_pipelining=false
+[docker:vars]
+ansible_host=ubuntu-latest
+ansible_connection=docker
+
+[libvirt_lxc]
+libvirt_lxc-pipelining ansible_ssh_pipelining=true
+libvirt_lxc-no-pipelining ansible_ssh_pipelining=false
+[libvirt_lxc:vars]
+ansible_host=lv-ubuntu-wily-amd64
+ansible_connection=libvirt_lxc
+
+[jail]
+jail-pipelining ansible_ssh_pipelining=true
+jail-no-pipelining ansible_ssh_pipelining=false
+[jail:vars]
+ansible_host=freebsd_10_2
+ansible_connection=jail
+ansible_python_interpreter=/usr/local/bin/python
+
+[ssh]
+ssh-pipelining ansible_ssh_pipelining=true
+ssh-no-pipelining ansible_ssh_pipelining=false
+[ssh:vars]
+ansible_host=localhost
+ansible_connection=ssh
+
+[paramiko_ssh]
+paramiko_ssh-pipelining ansible_ssh_pipelining=true
+paramiko_ssh-no-pipelining ansible_ssh_pipelining=false
+[paramiko_ssh:vars]
+ansible_host=localhost
+ansible_connection=paramiko_ssh
+
+[skip-during-build:children]
+docker
+libvirt_lxc
+jail
+ssh
+paramiko_ssh
diff --git a/test/integration/test_connection.yml b/test/integration/test_connection.yml
new file mode 100644
index 00000000000..27f24aafd6b
--- /dev/null
+++ b/test/integration/test_connection.yml
@@ -0,0 +1,37 @@
+- hosts: all
+ gather_facts: no
+ serial: 1
+ tasks:
+
+ ### raw with unicode arg and output
+
+ - name: raw with unicode arg and output
+ raw: echo 汉语
+ register: command
+ - name: check output of raw with unicode arg and output
+ assert: { that: "'汉语' in command.stdout" }
+
+ ### copy local file with unicode filename and content
+
+ - name: create local file with unicode filename and content
+ local_action: lineinfile dest=/tmp/ansible-local-汉语/汉语.txt create=true line=汉语
+ - name: remove remote file with unicode filename and content
+ file: path=/tmp/ansible-remote-汉语/汉语.txt state=absent
+ - name: create remote directory with unicode name
+ file: path=/tmp/ansible-remote-汉语 state=directory
+ - name: copy local file with unicode filename and content
+ copy: src=/tmp/ansible-local-汉语/汉语.txt dest=/tmp/ansible-remote-汉语/汉语.txt
+
+ ### fetch remote file with unicode filename and content
+
+ - name: remove local file with unicode filename and content
+ local_action: file path=/tmp/ansible-local-汉语/汉语.txt state=absent
+ - name: fetch remote file with unicode filename and content
+ fetch: src=/tmp/ansible-remote-汉语/汉语.txt dest=/tmp/ansible-local-汉语/汉语.txt fail_on_missing=true validate_checksum=true flat=true
+
+ ### remove local and remote temp files
+
+ - name: remove local temp file
+ local_action: file path=/tmp/ansible-local-汉语 state=absent
+ - name: remove remote temp file
+ file: path=/tmp/ansible-remote-汉语 state=absent
diff --git a/test/integration/test_gathering_facts.yml b/test/integration/test_gathering_facts.yml
new file mode 100644
index 00000000000..ed3fa841bab
--- /dev/null
+++ b/test/integration/test_gathering_facts.yml
@@ -0,0 +1,133 @@
+---
+
+- hosts: facthost0
+ tags: [ 'fact_min' ]
+ connection: local
+ gather_subset: "all"
+ gather_facts: yes
+ tasks:
+ - setup:
+ register: facts
+ - debug: var=facts
+ - name: Test that retrieving all facts works
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" != "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" != "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" != "UNDEF_VIRT"'
+
+- hosts: facthost1
+ tags: [ 'fact_min' ]
+ connection: local
+ gather_subset: "!all"
+ gather_facts: yes
+ tasks:
+ - name: Test that only retrieving minimal facts work
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" == "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" == "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" == "UNDEF_VIRT"'
+
+- hosts: facthost2
+ tags: [ 'fact_network' ]
+ connection: local
+ gather_subset: "network"
+ gather_facts: yes
+ tasks:
+ - name: Test that retrieving network facts work
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" != "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" == "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" == "UNDEF_VIRT"'
+
+- hosts: facthost3
+ tags: [ 'fact_hardware' ]
+ connection: local
+ gather_subset: "hardware"
+ gather_facts: yes
+ tasks:
+ - name: Test that retrieving hardware facts work
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" == "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" != "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" == "UNDEF_VIRT"'
+
+- hosts: facthost4
+ tags: [ 'fact_virtual' ]
+ connection: local
+ gather_subset: "virtual"
+ gather_facts: yes
+ tasks:
+ - name: Test that retrieving virtualization facts work
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" == "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" == "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" != "UNDEF_VIRT"'
+
+- hosts: facthost5
+ tags: [ 'fact_comma_string' ]
+ connection: local
+ gather_subset: "virtual,network"
+ gather_facts: yes
+ tasks:
+ - name: Test that retrieving virtualization and network as a string works
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" != "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" == "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" != "UNDEF_VIRT"'
+
+- hosts: facthost6
+ tags: [ 'fact_yaml_list' ]
+ connection: local
+ gather_subset:
+ - virtual
+ - network
+ gather_facts: yes
+ tasks:
+ - name: Test that retrieving virtualization and network as a string works
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" != "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" == "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" != "UNDEF_VIRT"'
+
+- hosts: facthost7
+ tags: [ 'fact_negation' ]
+ connection: local
+ gather_subset: "!hardware"
+ gather_facts: yes
+ tasks:
+ - name: Test that negation of fact subsets work
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" != "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" == "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" != "UNDEF_VIRT"'
+
+- hosts: facthost8
+ tags: [ 'fact_mixed_negation_addition' ]
+ connection: local
+ gather_subset: "!hardware,network"
+ gather_facts: yes
+ tasks:
+ - name: Test that negation and additional subsets work together
+ assert:
+ that:
+ - '"{{ ansible_user_id|default("UNDEF_MIN") }}" != "UNDEF_MIN"'
+ - '"{{ ansible_interfaces|default("UNDEF_NET") }}" != "UNDEF_NET"'
+ - '"{{ ansible_mounts|default("UNDEF_HW") }}" == "UNDEF_HW"'
+ - '"{{ ansible_virtualization_role|default("UNDEF_VIRT") }}" == "UNDEF_VIRT"'
+
diff --git a/test/integration/test_test_infra.yml b/test/integration/test_test_infra.yml
new file mode 100644
index 00000000000..48028ad8a7e
--- /dev/null
+++ b/test/integration/test_test_infra.yml
@@ -0,0 +1,26 @@
+- hosts: testhost
+ gather_facts: no
+ tags:
+ - always
+ tasks:
+ - name: ensure fail action produces a failing result
+ fail:
+ ignore_errors: yes
+ register: fail_out
+
+ - debug:
+ msg: fail works ({{ fail_out.failed }})
+
+ - name: ensure assert produces a failing result
+ assert:
+ that: false
+ ignore_errors: yes
+ register: assert_out
+
+ - debug:
+ msg: assert works ({{ assert_out.failed }})
+
+ - name: ensure fail action stops execution
+ fail:
+ msg: fail actually failed (this is expected)
+
diff --git a/test/integration/test_winrm.yml b/test/integration/test_winrm.yml
index f11171faf8c..51a5daa51fb 100644
--- a/test/integration/test_winrm.yml
+++ b/test/integration/test_winrm.yml
@@ -37,4 +37,5 @@
- { role: test_win_copy, tags: test_win_copy }
- { role: test_win_template, tags: test_win_template }
- { role: test_win_lineinfile, tags: test_win_lineinfile }
+ - { role: test_win_regmerge, tags: test_win_regmerge }
diff --git a/test/units/executor/test_play_iterator.py b/test/units/executor/test_play_iterator.py
index b2310fe242e..d8e0d97e021 100644
--- a/test/units/executor/test_play_iterator.py
+++ b/test/units/executor/test_play_iterator.py
@@ -23,8 +23,9 @@ from ansible.compat.tests import unittest
from ansible.compat.tests.mock import patch, MagicMock
from ansible.errors import AnsibleError, AnsibleParserError
-from ansible.executor.play_iterator import PlayIterator
+from ansible.executor.play_iterator import HostState, PlayIterator
from ansible.playbook import Playbook
+from ansible.playbook.task import Task
from ansible.playbook.play_context import PlayContext
from units.mock.loader import DictDataLoader
@@ -37,6 +38,23 @@ class TestPlayIterator(unittest.TestCase):
def tearDown(self):
pass
+ def test_host_state(self):
+ hs = HostState(blocks=[x for x in range(0, 10)])
+ hs.tasks_child_state = HostState(blocks=[0])
+ hs.rescue_child_state = HostState(blocks=[1])
+ hs.always_child_state = HostState(blocks=[2])
+ hs.__repr__()
+ hs.run_state = 100
+ hs.__repr__()
+ hs.fail_state = 15
+ hs.__repr__()
+
+ for i in range(0, 10):
+ hs.cur_block = i
+ self.assertEqual(hs.get_current_block(), i)
+
+ new_hs = hs.copy()
+
def test_play_iterator(self):
fake_loader = DictDataLoader({
"test_play.yml": """
@@ -48,6 +66,18 @@ class TestPlayIterator(unittest.TestCase):
- debug: msg="this is a pre_task"
tasks:
- debug: msg="this is a regular task"
+ - block:
+ - debug: msg="this is a block task"
+ - block:
+ - debug: msg="this is a sub-block in a block"
+ rescue:
+ - debug: msg="this is a rescue task"
+ - block:
+ - debug: msg="this is a sub-block in a rescue"
+ always:
+ - debug: msg="this is an always task"
+ - block:
+ - debug: msg="this is a sub-block in an always"
post_tasks:
- debug: msg="this is a post_task"
""",
@@ -64,10 +94,12 @@ class TestPlayIterator(unittest.TestCase):
hosts = []
for i in range(0, 10):
- host = MagicMock()
- host.get_name.return_value = 'host%02d' % i
+ host = MagicMock()
+ host.name = host.get_name.return_value = 'host%02d' % i
hosts.append(host)
+ mock_var_manager._fact_cache['host00'] = dict()
+
inventory = MagicMock()
inventory.get_hosts.return_value = hosts
inventory.filter_hosts.return_value = hosts
@@ -82,6 +114,16 @@ class TestPlayIterator(unittest.TestCase):
all_vars=dict(),
)
+ # lookup up an original task
+ target_task = p._entries[0].tasks[0].block[0]
+ task_copy = target_task.copy(exclude_block=True)
+ found_task = itr.get_original_task(hosts[0], task_copy)
+ self.assertEqual(target_task, found_task)
+
+ bad_task = Task()
+ found_task = itr.get_original_task(hosts[0], bad_task)
+ self.assertIsNone(found_task)
+
# pre task
(host_state, task) = itr.get_next_task_for_host(hosts[0])
self.assertIsNotNone(task)
@@ -100,6 +142,38 @@ class TestPlayIterator(unittest.TestCase):
self.assertIsNotNone(task)
self.assertEqual(task.action, 'debug')
self.assertIsNone(task._role)
+ # block task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg="this is a block task"))
+ # sub-block task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg="this is a sub-block in a block"))
+ # mark the host failed
+ itr.mark_host_failed(hosts[0])
+ # block rescue task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg="this is a rescue task"))
+ # sub-block rescue task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg="this is a sub-block in a rescue"))
+ # block always task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg="this is an always task"))
+ # sub-block always task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg="this is a sub-block in an always"))
# implicit meta: flush_handlers
(host_state, task) = itr.get_next_task_for_host(hosts[0])
self.assertIsNotNone(task)
@@ -116,3 +190,183 @@ class TestPlayIterator(unittest.TestCase):
(host_state, task) = itr.get_next_task_for_host(hosts[0])
self.assertIsNone(task)
+ # host 0 shouldn't be in the failed hosts, as the error
+ # was handled by a rescue block
+ failed_hosts = itr.get_failed_hosts()
+ self.assertNotIn(hosts[0], failed_hosts)
+
+ def test_play_iterator_nested_blocks(self):
+ fake_loader = DictDataLoader({
+ "test_play.yml": """
+ - hosts: all
+ gather_facts: false
+ tasks:
+ - block:
+ - block:
+ - block:
+ - block:
+ - block:
+ - debug: msg="this is the first task"
+ - ping:
+ rescue:
+ - block:
+ - block:
+ - block:
+ - block:
+ - debug: msg="this is the rescue task"
+ always:
+ - block:
+ - block:
+ - block:
+ - block:
+ - debug: msg="this is the always task"
+ """,
+ })
+
+ mock_var_manager = MagicMock()
+ mock_var_manager._fact_cache = dict()
+ mock_var_manager.get_vars.return_value = dict()
+
+ p = Playbook.load('test_play.yml', loader=fake_loader, variable_manager=mock_var_manager)
+
+ hosts = []
+ for i in range(0, 10):
+ host = MagicMock()
+ host.name = host.get_name.return_value = 'host%02d' % i
+ hosts.append(host)
+
+ inventory = MagicMock()
+ inventory.get_hosts.return_value = hosts
+ inventory.filter_hosts.return_value = hosts
+
+ play_context = PlayContext(play=p._entries[0])
+
+ itr = PlayIterator(
+ inventory=inventory,
+ play=p._entries[0],
+ play_context=play_context,
+ variable_manager=mock_var_manager,
+ all_vars=dict(),
+ )
+
+ # implicit meta: flush_handlers
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'meta')
+ self.assertEqual(task.args, dict(_raw_params='flush_handlers'))
+ # get the first task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg='this is the first task'))
+ # fail the host
+ itr.mark_host_failed(hosts[0])
+ # get the resuce task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg='this is the rescue task'))
+ # get the always task
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'debug')
+ self.assertEqual(task.args, dict(msg='this is the always task'))
+ # implicit meta: flush_handlers
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'meta')
+ self.assertEqual(task.args, dict(_raw_params='flush_handlers'))
+ # implicit meta: flush_handlers
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'meta')
+ self.assertEqual(task.args, dict(_raw_params='flush_handlers'))
+ # end of iteration
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNone(task)
+
+ def test_play_iterator_add_tasks(self):
+ fake_loader = DictDataLoader({
+ 'test_play.yml': """
+ - hosts: all
+ gather_facts: no
+ tasks:
+ - debug: msg="dummy task"
+ """,
+ })
+
+ mock_var_manager = MagicMock()
+ mock_var_manager._fact_cache = dict()
+ mock_var_manager.get_vars.return_value = dict()
+
+ p = Playbook.load('test_play.yml', loader=fake_loader, variable_manager=mock_var_manager)
+
+ hosts = []
+ for i in range(0, 10):
+ host = MagicMock()
+ host.name = host.get_name.return_value = 'host%02d' % i
+ hosts.append(host)
+
+ inventory = MagicMock()
+ inventory.get_hosts.return_value = hosts
+ inventory.filter_hosts.return_value = hosts
+
+ play_context = PlayContext(play=p._entries[0])
+
+ itr = PlayIterator(
+ inventory=inventory,
+ play=p._entries[0],
+ play_context=play_context,
+ variable_manager=mock_var_manager,
+ all_vars=dict(),
+ )
+
+ # test the high-level add_tasks() method
+ s = HostState(blocks=[0,1,2])
+ itr._insert_tasks_into_state = MagicMock(return_value=s)
+ itr.add_tasks(hosts[0], [3,4,5])
+ self.assertEqual(itr._host_states[hosts[0].name], s)
+
+ # now actually test the lower-level method that does the work
+ itr = PlayIterator(
+ inventory=inventory,
+ play=p._entries[0],
+ play_context=play_context,
+ variable_manager=mock_var_manager,
+ all_vars=dict(),
+ )
+
+ # iterate past first task
+ _, task = itr.get_next_task_for_host(hosts[0])
+ while(task and task.action != 'debug'):
+ _, task = itr.get_next_task_for_host(hosts[0])
+
+ if task is None:
+ raise Exception("iterated past end of play while looking for place to insert tasks")
+
+ # get the current host state and copy it so we can mutate it
+ s = itr.get_host_state(hosts[0])
+ s_copy = s.copy()
+
+ # assert with an empty task list, or if we're in a failed state, we simply return the state as-is
+ res_state = itr._insert_tasks_into_state(s_copy, task_list=[])
+ self.assertEqual(res_state, s_copy)
+
+ s_copy.fail_state = itr.FAILED_TASKS
+ res_state = itr._insert_tasks_into_state(s_copy, task_list=[MagicMock()])
+ self.assertEqual(res_state, s_copy)
+
+ # but if we've failed with a rescue/always block
+ mock_task = MagicMock()
+ s_copy.run_state = itr.ITERATING_RESCUE
+ res_state = itr._insert_tasks_into_state(s_copy, task_list=[mock_task])
+ self.assertEqual(res_state, s_copy)
+ self.assertIn(mock_task, res_state._blocks[res_state.cur_block].rescue)
+ itr._host_states[hosts[0].name] = res_state
+ (next_state, next_task) = itr.get_next_task_for_host(hosts[0], peek=True)
+ self.assertEqual(next_task, mock_task)
+ itr._host_states[hosts[0].name] = s
+
+ # test a regular insertion
+ s_copy = s.copy()
+ res_state = itr._insert_tasks_into_state(s_copy, task_list=[MagicMock()])
diff --git a/test/units/executor/test_task_result.py b/test/units/executor/test_task_result.py
new file mode 100644
index 00000000000..a0af67edbd1
--- /dev/null
+++ b/test/units/executor/test_task_result.py
@@ -0,0 +1,130 @@
+# (c) 2016, James Cammarata
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.compat.tests import unittest
+from ansible.compat.tests.mock import patch, MagicMock
+
+from ansible.executor.task_result import TaskResult
+
+class TestTaskResult(unittest.TestCase):
+ def test_task_result_basic(self):
+ mock_host = MagicMock()
+ mock_task = MagicMock()
+
+ # test loading a result with a dict
+ tr = TaskResult(mock_host, mock_task, dict())
+
+ # test loading a result with a JSON string
+ with patch('ansible.parsing.dataloader.DataLoader.load') as p:
+ tr = TaskResult(mock_host, mock_task, '{}')
+
+ def test_task_result_is_changed(self):
+ mock_host = MagicMock()
+ mock_task = MagicMock()
+
+ # test with no changed in result
+ tr = TaskResult(mock_host, mock_task, dict())
+ self.assertFalse(tr.is_changed())
+
+ # test with changed in the result
+ tr = TaskResult(mock_host, mock_task, dict(changed=True))
+ self.assertTrue(tr.is_changed())
+
+ # test with multiple results but none changed
+ mock_task.loop = 'foo'
+ tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]))
+ self.assertFalse(tr.is_changed())
+
+ # test with multiple results and one changed
+ mock_task.loop = 'foo'
+ tr = TaskResult(mock_host, mock_task, dict(results=[dict(changed=False), dict(changed=True), dict(some_key=False)]))
+ self.assertTrue(tr.is_changed())
+
+ def test_task_result_is_skipped(self):
+ mock_host = MagicMock()
+ mock_task = MagicMock()
+
+ # test with no skipped in result
+ tr = TaskResult(mock_host, mock_task, dict())
+ self.assertFalse(tr.is_skipped())
+
+ # test with skipped in the result
+ tr = TaskResult(mock_host, mock_task, dict(skipped=True))
+ self.assertTrue(tr.is_skipped())
+
+ # test with multiple results but none skipped
+ mock_task.loop = 'foo'
+ tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]))
+ self.assertFalse(tr.is_skipped())
+
+ # test with multiple results and one skipped
+ mock_task.loop = 'foo'
+ tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=False), dict(skipped=True), dict(some_key=False)]))
+ self.assertFalse(tr.is_skipped())
+
+ # test with multiple results and all skipped
+ mock_task.loop = 'foo'
+ tr = TaskResult(mock_host, mock_task, dict(results=[dict(skipped=True), dict(skipped=True), dict(skipped=True)]))
+ self.assertTrue(tr.is_skipped())
+
+ def test_task_result_is_unreachable(self):
+ mock_host = MagicMock()
+ mock_task = MagicMock()
+
+ # test with no unreachable in result
+ tr = TaskResult(mock_host, mock_task, dict())
+ self.assertFalse(tr.is_unreachable())
+
+ # test with unreachable in the result
+ tr = TaskResult(mock_host, mock_task, dict(unreachable=True))
+ self.assertTrue(tr.is_unreachable())
+
+ # test with multiple results but none unreachable
+ mock_task.loop = 'foo'
+ tr = TaskResult(mock_host, mock_task, dict(results=[dict(foo='bar'), dict(bam='baz'), True]))
+ self.assertFalse(tr.is_unreachable())
+
+ # test with multiple results and one unreachable
+ mock_task.loop = 'foo'
+ tr = TaskResult(mock_host, mock_task, dict(results=[dict(unreachable=False), dict(unreachable=True), dict(some_key=False)]))
+ self.assertTrue(tr.is_unreachable())
+
+ def test_task_result_is_failed(self):
+ mock_host = MagicMock()
+ mock_task = MagicMock()
+
+ # test with no failed in result
+ tr = TaskResult(mock_host, mock_task, dict())
+ self.assertFalse(tr.is_failed())
+
+ # test failed result with rc values
+ tr = TaskResult(mock_host, mock_task, dict(rc=0))
+ self.assertFalse(tr.is_failed())
+ tr = TaskResult(mock_host, mock_task, dict(rc=1))
+ self.assertTrue(tr.is_failed())
+
+ # test with failed in result
+ tr = TaskResult(mock_host, mock_task, dict(failed=True))
+ self.assertTrue(tr.is_failed())
+
+ # test with failed_when in result
+ tr = TaskResult(mock_host, mock_task, dict(failed_when_result=True))
+ self.assertTrue(tr.is_failed())
diff --git a/test/units/module_utils/basic/test__log_invocation.py b/test/units/module_utils/basic/test__log_invocation.py
new file mode 100644
index 00000000000..a08a2d84ca0
--- /dev/null
+++ b/test/units/module_utils/basic/test__log_invocation.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# (c) 2016, James Cammarata
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division)
+__metaclass__ = type
+
+import json
+
+from ansible.compat.tests import unittest
+from ansible.compat.tests.mock import MagicMock
+
+class TestModuleUtilsBasic(unittest.TestCase):
+
+ def test_module_utils_basic__log_invocation(self):
+ from ansible.module_utils import basic
+
+ # test basic log invocation
+ basic.MODULE_COMPLEX_ARGS = json.dumps(dict(foo=False, bar=[1,2,3], bam="bam", baz=u'baz'))
+ am = basic.AnsibleModule(
+ argument_spec=dict(
+ foo = dict(default=True, type='bool'),
+ bar = dict(default=[], type='list'),
+ bam = dict(default="bam"),
+ baz = dict(default=u"baz"),
+ password = dict(default=True),
+ no_log = dict(default="you shouldn't see me", no_log=True),
+ ),
+ )
+
+ am.log = MagicMock()
+ am._log_invocation()
+ am.log.assert_called_with(
+ 'Invoked with bam=bam bar=[1, 2, 3] foo=False baz=baz no_log=NOT_LOGGING_PARAMETER password=NOT_LOGGING_PASSWORD ',
+ log_args={
+ 'foo': 'False',
+ 'bar': '[1, 2, 3]',
+ 'bam': 'bam',
+ 'baz': 'baz',
+ 'password': 'NOT_LOGGING_PASSWORD',
+ 'no_log': 'NOT_LOGGING_PARAMETER',
+ },
+ )
diff --git a/test/units/module_utils/basic/test_exit_json.py b/test/units/module_utils/basic/test_exit_json.py
index 27bbb0f9e56..7d32c8082f4 100644
--- a/test/units/module_utils/basic/test_exit_json.py
+++ b/test/units/module_utils/basic/test_exit_json.py
@@ -23,9 +23,9 @@ __metaclass__ = type
import copy
import json
import sys
+from io import BytesIO
from ansible.compat.tests import unittest
-from ansible.compat.six import StringIO
from ansible.module_utils import basic
from ansible.module_utils.basic import heuristic_log_sanitize
@@ -41,7 +41,7 @@ class TestAnsibleModuleExitJson(unittest.TestCase):
basic.MODULE_COMPLEX_ARGS = '{}'
self.old_stdout = sys.stdout
- self.fake_stream = StringIO()
+ self.fake_stream = BytesIO()
sys.stdout = self.fake_stream
self.module = basic.AnsibleModule(argument_spec=dict())
@@ -127,7 +127,7 @@ class TestAnsibleModuleExitValuesRemoved(unittest.TestCase):
def test_exit_json_removes_values(self):
self.maxDiff = None
for args, return_val, expected in self.dataset:
- sys.stdout = StringIO()
+ sys.stdout = BytesIO()
basic.MODULE_COMPLEX_ARGS = json.dumps(args)
module = basic.AnsibleModule(
argument_spec = dict(
@@ -146,7 +146,7 @@ class TestAnsibleModuleExitValuesRemoved(unittest.TestCase):
expected = copy.deepcopy(expected)
del expected['changed']
expected['failed'] = True
- sys.stdout = StringIO()
+ sys.stdout = BytesIO()
basic.MODULE_COMPLEX_ARGS = json.dumps(args)
module = basic.AnsibleModule(
argument_spec = dict(
diff --git a/test/units/module_utils/basic/test_run_command.py b/test/units/module_utils/basic/test_run_command.py
index 0db6fbe7b94..191560e9616 100644
--- a/test/units/module_utils/basic/test_run_command.py
+++ b/test/units/module_utils/basic/test_run_command.py
@@ -22,16 +22,16 @@ __metaclass__ = type
import errno
import sys
import time
+from io import BytesIO
from ansible.compat.tests import unittest
-from ansible.compat.six import StringIO, BytesIO
from ansible.compat.tests.mock import call, MagicMock, Mock, patch, sentinel
from ansible.module_utils import basic
from ansible.module_utils.basic import AnsibleModule
-class OpenStringIO(StringIO):
- """StringIO with dummy close() method
+class OpenBytesIO(BytesIO):
+ """BytesIO with dummy close() method
So that you can inspect the content after close() was called.
"""
@@ -77,7 +77,7 @@ class TestAnsibleModuleRunCommand(unittest.TestCase):
self.subprocess = patch('ansible.module_utils.basic.subprocess').start()
self.cmd = Mock()
self.cmd.returncode = 0
- self.cmd.stdin = OpenStringIO()
+ self.cmd.stdin = OpenBytesIO()
self.cmd.stdout.fileno.return_value = sentinel.stdout
self.cmd.stderr.fileno.return_value = sentinel.stderr
self.subprocess.Popen.return_value = self.cmd
diff --git a/test/units/module_utils/basic/test_safe_eval.py b/test/units/module_utils/basic/test_safe_eval.py
new file mode 100644
index 00000000000..32a2c4c27a3
--- /dev/null
+++ b/test/units/module_utils/basic/test_safe_eval.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# (c) 2015, Toshio Kuratomi
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division)
+__metaclass__ = type
+
+from ansible.compat.tests import unittest
+
+
+class TestAnsibleModuleExitJson(unittest.TestCase):
+
+ def test_module_utils_basic_safe_eval(self):
+ from ansible.module_utils import basic
+
+ basic.MODULE_COMPLEX_ARGS = '{}'
+ am = basic.AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ # test some basic usage
+ # string (and with exceptions included), integer, bool
+ self.assertEqual(am.safe_eval("'a'"), 'a')
+ self.assertEqual(am.safe_eval("'a'", include_exceptions=True), ('a', None))
+ self.assertEqual(am.safe_eval("1"), 1)
+ self.assertEqual(am.safe_eval("True"), True)
+ self.assertEqual(am.safe_eval("False"), False)
+ self.assertEqual(am.safe_eval("{}"), {})
+ # not passing in a string to convert
+ self.assertEqual(am.safe_eval({'a':1}), {'a':1})
+ self.assertEqual(am.safe_eval({'a':1}, include_exceptions=True), ({'a':1}, None))
+ # invalid literal eval
+ self.assertEqual(am.safe_eval("a=1"), "a=1")
+ res = am.safe_eval("a=1", include_exceptions=True)
+ self.assertEqual(res[0], "a=1")
+ self.assertEqual(type(res[1]), SyntaxError)
+ self.assertEqual(am.safe_eval("a.foo()"), "a.foo()")
+ res = am.safe_eval("a.foo()", include_exceptions=True)
+ self.assertEqual(res[0], "a.foo()")
+ self.assertEqual(res[1], None)
+ self.assertEqual(am.safe_eval("import foo"), "import foo")
+ res = am.safe_eval("import foo", include_exceptions=True)
+ self.assertEqual(res[0], "import foo")
+ self.assertEqual(res[1], None)
+ self.assertEqual(am.safe_eval("__import__('foo')"), "__import__('foo')")
+ res = am.safe_eval("__import__('foo')", include_exceptions=True)
+ self.assertEqual(res[0], "__import__('foo')")
+ self.assertEqual(type(res[1]), ValueError)
+
diff --git a/test/units/module_utils/test_basic.py b/test/units/module_utils/test_basic.py
index 86473dd2037..0a4ed0763d8 100644
--- a/test/units/module_utils/test_basic.py
+++ b/test/units/module_utils/test_basic.py
@@ -21,12 +21,18 @@ from __future__ import (absolute_import, division)
__metaclass__ = type
import errno
+import os
import sys
-from six.moves import builtins
+try:
+ import builtins
+except ImportError:
+ import __builtin__ as builtins
from ansible.compat.tests import unittest
-from ansible.compat.tests.mock import patch, MagicMock, mock_open, Mock
+from ansible.compat.tests.mock import patch, MagicMock, mock_open, Mock, call
+
+realimport = builtins.__import__
class TestModuleUtilsBasic(unittest.TestCase):
@@ -36,17 +42,114 @@ class TestModuleUtilsBasic(unittest.TestCase):
def tearDown(self):
pass
- def test_module_utils_basic_imports(self):
- realimport = builtins.__import__
+ def clear_modules(self, mods):
+ for mod in mods:
+ if mod in sys.modules:
+ del sys.modules[mod]
+
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_syslog(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ if name == 'syslog':
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ self.clear_modules(['syslog', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertTrue(mod.module_utils.basic.HAS_SYSLOG)
+
+ self.clear_modules(['syslog', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertFalse(mod.module_utils.basic.HAS_SYSLOG)
+
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_selinux(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ if name == 'selinux':
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ try:
+ self.clear_modules(['selinux', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertTrue(mod.module_utils.basic.HAVE_SELINUX)
+ except ImportError:
+ # no selinux on test system, so skip
+ pass
+
+ self.clear_modules(['selinux', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertFalse(mod.module_utils.basic.HAVE_SELINUX)
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_json(self, mock_import):
def _mock_import(name, *args, **kwargs):
if name == 'json':
- raise ImportError()
- realimport(name, *args, **kwargs)
+ raise ImportError
+ elif name == 'simplejson':
+ return MagicMock()
+ return realimport(name, *args, **kwargs)
- with patch.object(builtins, '__import__', _mock_import, create=True) as m:
- m('ansible.module_utils.basic')
- builtins.__import__('ansible.module_utils.basic')
+ self.clear_modules(['json', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+
+ self.clear_modules(['json', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ mod = builtins.__import__('ansible.module_utils.basic')
+
+ # FIXME: doesn't work yet
+ #@patch.object(builtins, 'bytes')
+ #def test_module_utils_basic_bytes(self, mock_bytes):
+ # mock_bytes.side_effect = NameError()
+ # from ansible.module_utils import basic
+
+ @patch.object(builtins, '__import__')
+ @unittest.skipIf(sys.version_info[0] >= 3, "Python 3 is not supported on targets (yet)")
+ def test_module_utils_basic_import_literal_eval(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ try:
+ fromlist = kwargs.get('fromlist', args[2])
+ except IndexError:
+ fromlist = []
+ if name == 'ast' and 'literal_eval' in fromlist:
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ mock_import.side_effect = _mock_import
+ self.clear_modules(['ast', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertEqual(mod.module_utils.basic.literal_eval("'1'"), "1")
+ self.assertEqual(mod.module_utils.basic.literal_eval("1"), 1)
+ self.assertEqual(mod.module_utils.basic.literal_eval("-1"), -1)
+ self.assertEqual(mod.module_utils.basic.literal_eval("(1,2,3)"), (1,2,3))
+ self.assertEqual(mod.module_utils.basic.literal_eval("[1]"), [1])
+ self.assertEqual(mod.module_utils.basic.literal_eval("True"), True)
+ self.assertEqual(mod.module_utils.basic.literal_eval("False"), False)
+ self.assertEqual(mod.module_utils.basic.literal_eval("None"), None)
+ #self.assertEqual(mod.module_utils.basic.literal_eval('{"a": 1}'), dict(a=1))
+ self.assertRaises(ValueError, mod.module_utils.basic.literal_eval, "asdfasdfasdf")
+
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_systemd_journal(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ try:
+ fromlist = kwargs.get('fromlist', args[2])
+ except IndexError:
+ fromlist = []
+ if name == 'systemd' and 'journal' in fromlist:
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ self.clear_modules(['systemd', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertTrue(mod.module_utils.basic.has_journal)
+
+ self.clear_modules(['systemd', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertFalse(mod.module_utils.basic.has_journal)
def test_module_utils_basic_get_platform(self):
with patch('platform.system', return_value='foo'):
@@ -60,19 +163,19 @@ class TestModuleUtilsBasic(unittest.TestCase):
self.assertEqual(get_distribution(), None)
with patch('platform.system', return_value='Linux'):
- with patch('platform.linux_distribution', return_value=("foo", "1", "One")):
+ with patch('platform.linux_distribution', return_value=["foo"]):
self.assertEqual(get_distribution(), "Foo")
with patch('os.path.isfile', return_value=True):
- def _dist(distname='', version='', id='', supported_dists=(), full_distribution_name=1):
- if supported_dists != ():
- return ("AmazonFooBar", "", "")
- else:
- return ("", "", "")
-
- with patch('platform.linux_distribution', side_effect=_dist):
+ with patch('platform.linux_distribution', side_effect=[("AmazonFooBar",)]):
self.assertEqual(get_distribution(), "Amazonfoobar")
+ with patch('platform.linux_distribution', side_effect=(("",), ("AmazonFooBam",))):
+ self.assertEqual(get_distribution(), "Amazon")
+
+ with patch('platform.linux_distribution', side_effect=[("",),("",)]):
+ self.assertEqual(get_distribution(), "OtherLinux")
+
def _dist(distname='', version='', id='', supported_dists=(), full_distribution_name=1):
if supported_dists != ():
return ("Bar", "2", "Two")
@@ -678,17 +781,269 @@ class TestModuleUtilsBasic(unittest.TestCase):
self.assertEqual(am.set_mode_if_different('/path/to/file', 0o660, False), True)
am.check_mode = False
+ original_hasattr = hasattr
+ def _hasattr(obj, name):
+ if obj == os and name == 'lchmod':
+ return False
+ return original_hasattr(obj, name)
+
# FIXME: this isn't working yet
- #with patch('os.lstat', side_effect=[mock_stat1, mock_stat2]):
- # with patch('os.lchmod', return_value=None) as m_os:
- # del m_os.lchmod
- # with patch('os.path.islink', return_value=False):
- # with patch('os.chmod', return_value=None) as m_chmod:
- # self.assertEqual(am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False), True)
- # m_chmod.assert_called_with('/path/to/file', 0o660)
- # with patch('os.path.islink', return_value=True):
- # with patch('os.chmod', return_value=None) as m_chmod:
- # with patch('os.stat', return_value=mock_stat2):
- # self.assertEqual(am.set_mode_if_different('/path/to/file', 0o660, False), True)
- # m_chmod.assert_called_with('/path/to/file', 0o660)
+ with patch('os.lstat', side_effect=[mock_stat1, mock_stat2]):
+ with patch.object(builtins, 'hasattr', side_effect=_hasattr):
+ with patch('os.path.islink', return_value=False):
+ with patch('os.chmod', return_value=None) as m_chmod:
+ self.assertEqual(am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False), True)
+ with patch('os.lstat', side_effect=[mock_stat1, mock_stat2]):
+ with patch.object(builtins, 'hasattr', side_effect=_hasattr):
+ with patch('os.path.islink', return_value=True):
+ with patch('os.chmod', return_value=None) as m_chmod:
+ with patch('os.stat', return_value=mock_stat2):
+ self.assertEqual(am.set_mode_if_different('/path/to/file', 0o660, False), True)
+
+ @patch('tempfile.NamedTemporaryFile')
+ @patch('os.umask')
+ @patch('shutil.copyfileobj')
+ @patch('shutil.move')
+ @patch('shutil.copy2')
+ @patch('os.rename')
+ @patch('pwd.getpwuid')
+ @patch('os.getuid')
+ @patch('os.environ')
+ @patch('os.getlogin')
+ @patch('os.chown')
+ @patch('os.chmod')
+ @patch('os.stat')
+ @patch('os.path.exists')
+ def test_module_utils_basic_ansible_module_atomic_move(
+ self,
+ _os_path_exists,
+ _os_stat,
+ _os_chmod,
+ _os_chown,
+ _os_getlogin,
+ _os_environ,
+ _os_getuid,
+ _pwd_getpwuid,
+ _os_rename,
+ _shutil_copy2,
+ _shutil_move,
+ _shutil_copyfileobj,
+ _os_umask,
+ _tempfile_NamedTemporaryFile,
+ ):
+
+ from ansible.module_utils import basic
+
+ basic.MODULE_COMPLEX_ARGS = '{}'
+ am = basic.AnsibleModule(
+ argument_spec = dict(),
+ )
+
+ environ = dict()
+ _os_environ.__getitem__ = environ.__getitem__
+ _os_environ.__setitem__ = environ.__setitem__
+
+ am.selinux_enabled = MagicMock()
+ am.selinux_context = MagicMock()
+ am.selinux_default_context = MagicMock()
+ am.set_context_if_different = MagicMock()
+
+ # test destination does not exist, no selinux, login name = 'root',
+ # no environment, os.rename() succeeds
+ _os_path_exists.side_effect = [False, False]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_rename.return_value = None
+ _os_umask.side_effect = [18, 0]
+ am.selinux_enabled.return_value = False
+ _os_chmod.reset_mock()
+ _os_chown.reset_mock()
+ am.set_context_if_different.reset_mock()
+ am.atomic_move('/path/to/src', '/path/to/dest')
+ _os_rename.assert_called_with('/path/to/src', '/path/to/dest')
+ self.assertEqual(_os_chmod.call_args_list, [call('/path/to/dest', basic.DEFAULT_PERM & ~18)])
+
+ # same as above, except selinux_enabled
+ _os_path_exists.side_effect = [False, False]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_rename.return_value = None
+ _os_umask.side_effect = [18, 0]
+ mock_context = MagicMock()
+ am.selinux_default_context.return_value = mock_context
+ am.selinux_enabled.return_value = True
+ _os_chmod.reset_mock()
+ _os_chown.reset_mock()
+ am.set_context_if_different.reset_mock()
+ am.selinux_default_context.reset_mock()
+ am.atomic_move('/path/to/src', '/path/to/dest')
+ _os_rename.assert_called_with('/path/to/src', '/path/to/dest')
+ self.assertEqual(_os_chmod.call_args_list, [call('/path/to/dest', basic.DEFAULT_PERM & ~18)])
+ self.assertEqual(am.selinux_default_context.call_args_list, [call('/path/to/dest')])
+ self.assertEqual(am.set_context_if_different.call_args_list, [call('/path/to/dest', mock_context, False)])
+
+ # now with dest present, no selinux, also raise OSError when using
+ # os.getlogin() to test corner case with no tty
+ _os_path_exists.side_effect = [True, True]
+ _os_getlogin.side_effect = OSError()
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_rename.return_value = None
+ _os_umask.side_effect = [18, 0]
+ environ['LOGNAME'] = 'root'
+ stat1 = MagicMock()
+ stat1.st_mode = 0o0644
+ stat1.st_uid = 0
+ stat1.st_gid = 0
+ _os_stat.side_effect = [stat1,]
+ am.selinux_enabled.return_value = False
+ _os_chmod.reset_mock()
+ _os_chown.reset_mock()
+ am.set_context_if_different.reset_mock()
+ am.atomic_move('/path/to/src', '/path/to/dest')
+ _os_rename.assert_called_with('/path/to/src', '/path/to/dest')
+
+ # dest missing, selinux enabled
+ _os_path_exists.side_effect = [True, True]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_rename.return_value = None
+ _os_umask.side_effect = [18, 0]
+ stat1 = MagicMock()
+ stat1.st_mode = 0o0644
+ stat1.st_uid = 0
+ stat1.st_gid = 0
+ _os_stat.side_effect = [stat1,]
+ mock_context = MagicMock()
+ am.selinux_context.return_value = mock_context
+ am.selinux_enabled.return_value = True
+ _os_chmod.reset_mock()
+ _os_chown.reset_mock()
+ am.set_context_if_different.reset_mock()
+ am.selinux_default_context.reset_mock()
+ am.atomic_move('/path/to/src', '/path/to/dest')
+ _os_rename.assert_called_with('/path/to/src', '/path/to/dest')
+ self.assertEqual(am.selinux_context.call_args_list, [call('/path/to/dest')])
+ self.assertEqual(am.set_context_if_different.call_args_list, [call('/path/to/dest', mock_context, False)])
+
+ # now testing with exceptions raised
+ # have os.stat raise OSError which is not EPERM
+ _os_stat.side_effect = OSError()
+ _os_path_exists.side_effect = [True, True]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_rename.return_value = None
+ _os_umask.side_effect = [18, 0]
+ self.assertRaises(OSError, am.atomic_move, '/path/to/src', '/path/to/dest')
+
+ # and now have os.stat return EPERM, which should not fail
+ _os_stat.side_effect = OSError(errno.EPERM, 'testing os stat with EPERM')
+ _os_path_exists.side_effect = [True, True]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_rename.return_value = None
+ _os_umask.side_effect = [18, 0]
+ # FIXME: we don't assert anything here yet
+ am.atomic_move('/path/to/src', '/path/to/dest')
+
+ # now we test os.rename() raising errors...
+ # first we test with a bad errno to verify it bombs out
+ _os_path_exists.side_effect = [False, False]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_umask.side_effect = [18, 0]
+ _os_rename.side_effect = OSError(errno.EIO, 'failing with EIO')
+ self.assertRaises(SystemExit, am.atomic_move, '/path/to/src', '/path/to/dest')
+
+ # next we test with EPERM so it continues to the alternate code for moving
+ # test with NamedTemporaryFile raising an error first
+ _os_path_exists.side_effect = [False, False]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_umask.side_effect = [18, 0]
+ _os_rename.side_effect = [OSError(errno.EPERM, 'failing with EPERM'), None]
+ _tempfile_NamedTemporaryFile.return_value = None
+ _tempfile_NamedTemporaryFile.side_effect = OSError()
+ am.selinux_enabled.return_value = False
+ self.assertRaises(SystemExit, am.atomic_move, '/path/to/src', '/path/to/dest')
+
+ # then test with it creating a temp file
+ _os_path_exists.side_effect = [False, False]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_umask.side_effect = [18, 0]
+ _os_rename.side_effect = [OSError(errno.EPERM, 'failing with EPERM'), None]
+ mock_stat1 = MagicMock()
+ mock_stat2 = MagicMock()
+ mock_stat3 = MagicMock()
+ _os_stat.return_value = [mock_stat1, mock_stat2, mock_stat3]
+ _os_stat.side_effect = None
+ mock_tempfile = MagicMock()
+ mock_tempfile.name = '/path/to/tempfile'
+ _tempfile_NamedTemporaryFile.return_value = mock_tempfile
+ _tempfile_NamedTemporaryFile.side_effect = None
+ am.selinux_enabled.return_value = False
+ # FIXME: we don't assert anything here yet
+ am.atomic_move('/path/to/src', '/path/to/dest')
+
+ # same as above, but with selinux enabled
+ _os_path_exists.side_effect = [False, False]
+ _os_getlogin.return_value = 'root'
+ _os_getuid.return_value = 0
+ _pwd_getpwuid.return_value = ('root', '', 0, 0, '', '', '')
+ _os_umask.side_effect = [18, 0]
+ _os_rename.side_effect = [OSError(errno.EPERM, 'failing with EPERM'), None]
+ mock_tempfile = MagicMock()
+ _tempfile_NamedTemporaryFile.return_value = mock_tempfile
+ mock_context = MagicMock()
+ am.selinux_default_context.return_value = mock_context
+ am.selinux_enabled.return_value = True
+ am.atomic_move('/path/to/src', '/path/to/dest')
+
+ def test_module_utils_basic_ansible_module__symbolic_mode_to_octal(self):
+
+ from ansible.module_utils import basic
+
+ basic.MODULE_COMPLEX_ARGS = '{}'
+ am = basic.AnsibleModule(
+ argument_spec = dict(),
+ )
+
+ mock_stat = MagicMock()
+ # FIXME: trying many more combinations here would be good
+ # directory, give full perms to all, then one group at a time
+ mock_stat.st_mode = 0o040000
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'a+rwx'), 0o0777)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'u+rwx,g+rwx,o+rwx'), 0o0777)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'o+rwx'), 0o0007)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'g+rwx'), 0o0070)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'u+rwx'), 0o0700)
+
+ # same as above, but in reverse so removing permissions
+ mock_stat.st_mode = 0o040777
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'a-rwx'), 0o0000)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'u-rwx,g-rwx,o-rwx'), 0o0000)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'o-rwx'), 0o0770)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'g-rwx'), 0o0707)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'u-rwx'), 0o0077)
+
+ # now using absolute assignment
+ mock_stat.st_mode = 0o040000
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'a=rwx'), 0o0777)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'u=rwx,g=rwx,o=rwx'), 0o0777)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'o=rwx'), 0o0007)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'g=rwx'), 0o0070)
+ self.assertEqual(am._symbolic_mode_to_octal(mock_stat, 'u=rwx'), 0o0700)
+
+ # invalid modes
+ mock_stat.st_mode = 0o0400000
+ self.assertRaises(ValueError, am._symbolic_mode_to_octal, mock_stat, 'a=foo')
diff --git a/test/units/parsing/yaml/test_loader.py b/test/units/parsing/yaml/test_loader.py
index 8fd617eea19..253ba4d5914 100644
--- a/test/units/parsing/yaml/test_loader.py
+++ b/test/units/parsing/yaml/test_loader.py
@@ -20,8 +20,9 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+from io import StringIO
+
from six import text_type, binary_type
-from six.moves import StringIO
from collections import Sequence, Set, Mapping
from ansible.compat.tests import unittest
@@ -35,6 +36,13 @@ except ImportError:
from yaml.parser import ParserError
+class NameStringIO(StringIO):
+ """In py2.6, StringIO doesn't let you set name because a baseclass has it
+ as readonly property"""
+ name = None
+ def __init__(self, *args, **kwargs):
+ super(NameStringIO, self).__init__(*args, **kwargs)
+
class TestAnsibleLoaderBasic(unittest.TestCase):
def setUp(self):
@@ -44,7 +52,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
pass
def test_parse_number(self):
- stream = StringIO("""
+ stream = StringIO(u"""
1
""")
loader = AnsibleLoader(stream, 'myfile.yml')
@@ -53,7 +61,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
# No line/column info saved yet
def test_parse_string(self):
- stream = StringIO("""
+ stream = StringIO(u"""
Ansible
""")
loader = AnsibleLoader(stream, 'myfile.yml')
@@ -64,7 +72,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
self.assertEqual(data.ansible_pos, ('myfile.yml', 2, 17))
def test_parse_utf8_string(self):
- stream = StringIO("""
+ stream = StringIO(u"""
Cafè Eñyei
""")
loader = AnsibleLoader(stream, 'myfile.yml')
@@ -75,7 +83,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
self.assertEqual(data.ansible_pos, ('myfile.yml', 2, 17))
def test_parse_dict(self):
- stream = StringIO("""
+ stream = StringIO(u"""
webster: daniel
oed: oxford
""")
@@ -93,7 +101,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
self.assertEqual(data[u'oed'].ansible_pos, ('myfile.yml', 3, 22))
def test_parse_list(self):
- stream = StringIO("""
+ stream = StringIO(u"""
- a
- b
""")
@@ -109,7 +117,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
self.assertEqual(data[1].ansible_pos, ('myfile.yml', 3, 19))
def test_parse_short_dict(self):
- stream = StringIO("""{"foo": "bar"}""")
+ stream = StringIO(u"""{"foo": "bar"}""")
loader = AnsibleLoader(stream, 'myfile.yml')
data = loader.get_single_data()
self.assertEqual(data, dict(foo=u'bar'))
@@ -117,7 +125,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
self.assertEqual(data.ansible_pos, ('myfile.yml', 1, 1))
self.assertEqual(data[u'foo'].ansible_pos, ('myfile.yml', 1, 9))
- stream = StringIO("""foo: bar""")
+ stream = StringIO(u"""foo: bar""")
loader = AnsibleLoader(stream, 'myfile.yml')
data = loader.get_single_data()
self.assertEqual(data, dict(foo=u'bar'))
@@ -126,12 +134,12 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
self.assertEqual(data[u'foo'].ansible_pos, ('myfile.yml', 1, 6))
def test_error_conditions(self):
- stream = StringIO("""{""")
+ stream = StringIO(u"""{""")
loader = AnsibleLoader(stream, 'myfile.yml')
self.assertRaises(ParserError, loader.get_single_data)
def test_front_matter(self):
- stream = StringIO("""---\nfoo: bar""")
+ stream = StringIO(u"""---\nfoo: bar""")
loader = AnsibleLoader(stream, 'myfile.yml')
data = loader.get_single_data()
self.assertEqual(data, dict(foo=u'bar'))
@@ -140,7 +148,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
self.assertEqual(data[u'foo'].ansible_pos, ('myfile.yml', 2, 6))
# Initial indent (See: #6348)
- stream = StringIO(""" - foo: bar\n baz: qux""")
+ stream = StringIO(u""" - foo: bar\n baz: qux""")
loader = AnsibleLoader(stream, 'myfile.yml')
data = loader.get_single_data()
self.assertEqual(data, [{u'foo': u'bar', u'baz': u'qux'}])
@@ -154,7 +162,7 @@ class TestAnsibleLoaderBasic(unittest.TestCase):
class TestAnsibleLoaderPlay(unittest.TestCase):
def setUp(self):
- stream = StringIO("""
+ stream = NameStringIO(u"""
- hosts: localhost
vars:
number: 1
diff --git a/test/units/plugins/action/test_action.py b/test/units/plugins/action/test_action.py
index 7fe216b5990..4bb151f090f 100644
--- a/test/units/plugins/action/test_action.py
+++ b/test/units/plugins/action/test_action.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
# (c) 2015, Florian Apolloner
#
@@ -21,15 +21,128 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+import ast
+import json
+import pipes
import os
+from sys import version_info
+
+try:
+ import builtins
+except ImportError:
+ import __builtin__ as builtins
+
+from ansible import __version__ as ansible_version
from ansible import constants as C
-from ansible.errors import AnsibleError
+from ansible.compat.six import text_type
from ansible.compat.tests import unittest
-from ansible.compat.tests.mock import patch, MagicMock, Mock
+from ansible.compat.tests.mock import patch, MagicMock, mock_open
+
+from ansible.errors import AnsibleError
from ansible.playbook.play_context import PlayContext
+from ansible.plugins import PluginLoader
from ansible.plugins.action import ActionBase
+from ansible.template import Templar
+from ansible.utils.unicode import to_bytes
+
+from units.mock.loader import DictDataLoader
+
+python_module_replacers = b"""
+#!/usr/bin/python
+#ANSIBLE_VERSION = "<>"
+#MODULE_ARGS = "<>"
+#MODULE_COMPLEX_ARGS = "<>"
+#SELINUX_SPECIAL_FS="<>"
+
+test = u'Toshio \u304f\u3089\u3068\u307f'
+from ansible.module_utils.basic import *
+"""
+
+powershell_module_replacers = b"""
+WINDOWS_ARGS = "<>"
+# POWERSHELL_COMMON
+"""
+
+# Prior to 3.4.4, mock_open cannot handle binary read_data
+if version_info >= (3,) and version_info < (3, 4, 4):
+ file_spec = None
+
+ def _iterate_read_data(read_data):
+ # Helper for mock_open:
+ # Retrieve lines from read_data via a generator so that separate calls to
+ # readline, read, and readlines are properly interleaved
+ sep = b'\n' if isinstance(read_data, bytes) else '\n'
+ data_as_list = [l + sep for l in read_data.split(sep)]
+
+ if data_as_list[-1] == sep:
+ # If the last line ended in a newline, the list comprehension will have an
+ # extra entry that's just a newline. Remove this.
+ data_as_list = data_as_list[:-1]
+ else:
+ # If there wasn't an extra newline by itself, then the file being
+ # emulated doesn't have a newline to end the last line remove the
+ # newline that our naive format() added
+ data_as_list[-1] = data_as_list[-1][:-1]
+
+ for line in data_as_list:
+ yield line
+
+ def mock_open(mock=None, read_data=''):
+ """
+ A helper function to create a mock to replace the use of `open`. It works
+ for `open` called directly or used as a context manager.
+
+ The `mock` argument is the mock object to configure. If `None` (the
+ default) then a `MagicMock` will be created for you, with the API limited
+ to methods or attributes available on standard file handles.
+
+ `read_data` is a string for the `read` methoddline`, and `readlines` of the
+ file handle to return. This is an empty string by default.
+ """
+ def _readlines_side_effect(*args, **kwargs):
+ if handle.readlines.return_value is not None:
+ return handle.readlines.return_value
+ return list(_data)
+
+ def _read_side_effect(*args, **kwargs):
+ if handle.read.return_value is not None:
+ return handle.read.return_value
+ return type(read_data)().join(_data)
+
+ def _readline_side_effect():
+ if handle.readline.return_value is not None:
+ while True:
+ yield handle.readline.return_value
+ for line in _data:
+ yield line
+
+
+ global file_spec
+ if file_spec is None:
+ import _io
+ file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
+
+ if mock is None:
+ mock = MagicMock(name='open', spec=open)
+
+ handle = MagicMock(spec=file_spec)
+ handle.__enter__.return_value = handle
+
+ _data = _iterate_read_data(read_data)
+
+ handle.write.return_value = None
+ handle.read.return_value = None
+ handle.readline.return_value = None
+ handle.readlines.return_value = None
+
+ handle.read.side_effect = _read_side_effect
+ handle.readline.side_effect = _readline_side_effect()
+ handle.readlines.side_effect = _readlines_side_effect
+
+ mock.return_value = handle
+ return mock
class DerivedActionBase(ActionBase):
@@ -41,11 +154,137 @@ class DerivedActionBase(ActionBase):
class TestActionBase(unittest.TestCase):
- class DerivedActionBase(ActionBase):
- def run(self, tmp=None, task_vars=None):
- # We're not testing the plugin run() method, just the helper
- # methods ActionBase defines
- return dict()
+ def test_action_base_run(self):
+ mock_task = MagicMock()
+ mock_task.action = "foo"
+ mock_task.args = dict(a=1, b=2, c=3)
+
+ mock_connection = MagicMock()
+
+ play_context = PlayContext()
+
+ mock_task.async = None
+ action_base = DerivedActionBase(mock_task, mock_connection, play_context, None, None, None)
+ results = action_base.run()
+ self.assertEqual(results, dict())
+
+ mock_task.async = 0
+ action_base = DerivedActionBase(mock_task, mock_connection, play_context, None, None, None)
+ results = action_base.run()
+ self.assertEqual(results, dict(invocation=dict(module_name='foo', module_args=dict(a=1, b=2, c=3))))
+
+ def test_action_base__configure_module(self):
+ fake_loader = DictDataLoader({
+ })
+
+ # create our fake task
+ mock_task = MagicMock()
+ mock_task.action = "copy"
+
+ # create a mock connection, so we don't actually try and connect to things
+ mock_connection = MagicMock()
+
+ # create a mock shared loader object
+ def mock_find_plugin(name, options):
+ if name == 'badmodule':
+ return None
+ elif '.ps1' in options:
+ return '/fake/path/to/%s.ps1' % name
+ else:
+ return '/fake/path/to/%s' % name
+
+ mock_module_loader = MagicMock()
+ mock_module_loader.find_plugin.side_effect = mock_find_plugin
+ mock_shared_obj_loader = MagicMock()
+ mock_shared_obj_loader.module_loader = mock_module_loader
+
+ # we're using a real play context here
+ play_context = PlayContext()
+
+ # our test class
+ action_base = DerivedActionBase(
+ task=mock_task,
+ connection=mock_connection,
+ play_context=play_context,
+ loader=fake_loader,
+ templar=None,
+ shared_loader_obj=mock_shared_obj_loader,
+ )
+
+ # test python module formatting
+ with patch.object(builtins, 'open', mock_open(read_data=to_bytes(python_module_replacers.strip(), encoding='utf-8'))) as m:
+ mock_task.args = dict(a=1, foo='fö〩')
+ mock_connection.module_implementation_preferences = ('',)
+ (style, shebang, data) = action_base._configure_module(mock_task.action, mock_task.args)
+ self.assertEqual(style, "new")
+ self.assertEqual(shebang, b"#!/usr/bin/python")
+
+ # test module not found
+ self.assertRaises(AnsibleError, action_base._configure_module, 'badmodule', mock_task.args)
+
+ # test powershell module formatting
+ with patch.object(builtins, 'open', mock_open(read_data=to_bytes(powershell_module_replacers.strip(), encoding='utf-8'))) as m:
+ mock_task.action = 'win_copy'
+ mock_task.args = dict(b=2)
+ mock_connection.module_implementation_preferences = ('.ps1',)
+ (style, shebang, data) = action_base._configure_module('stat', mock_task.args)
+ self.assertEqual(style, "new")
+ self.assertEqual(shebang, None)
+
+ # test module not found
+ self.assertRaises(AnsibleError, action_base._configure_module, 'badmodule', mock_task.args)
+
+ def test_action_base__compute_environment_string(self):
+ fake_loader = DictDataLoader({
+ })
+
+ # create our fake task
+ mock_task = MagicMock()
+ mock_task.action = "copy"
+ mock_task.args = dict(a=1)
+
+ # create a mock connection, so we don't actually try and connect to things
+ def env_prefix(**args):
+ return ' '.join(['%s=%s' % (k, pipes.quote(text_type(v))) for k,v in args.items()])
+ mock_connection = MagicMock()
+ mock_connection._shell.env_prefix.side_effect = env_prefix
+
+ # we're using a real play context here
+ play_context = PlayContext()
+
+ # and we're using a real templar here too
+ templar = Templar(loader=fake_loader)
+
+ # our test class
+ action_base = DerivedActionBase(
+ task=mock_task,
+ connection=mock_connection,
+ play_context=play_context,
+ loader=fake_loader,
+ templar=templar,
+ shared_loader_obj=None,
+ )
+
+ # test standard environment setup
+ mock_task.environment = [dict(FOO='foo'), None]
+ env_string = action_base._compute_environment_string()
+ self.assertEqual(env_string, "FOO=foo")
+
+ # test where environment is not a list
+ mock_task.environment = dict(FOO='foo')
+ env_string = action_base._compute_environment_string()
+ self.assertEqual(env_string, "FOO=foo")
+
+ # test environment with a variable in it
+ templar.set_available_variables(variables=dict(the_var='bar'))
+ mock_task.environment = [dict(FOO='{{the_var}}')]
+ env_string = action_base._compute_environment_string()
+ self.assertEqual(env_string, "FOO=bar")
+
+ # test with a bad environment set
+ mock_task.environment = dict(FOO='foo')
+ mock_task.environment = ['hi there']
+ self.assertRaises(AnsibleError, action_base._compute_environment_string)
def test_action_base__early_needs_tmp_path(self):
# create our fake task
@@ -240,8 +479,8 @@ class TestActionBase(unittest.TestCase):
self.assertEqual(action_base._transfer_data('/path/to/remote/file', 'some data'), '/path/to/remote/file')
self.assertEqual(action_base._transfer_data('/path/to/remote/file', 'some mixed data: fö〩'), '/path/to/remote/file')
- self.assertEqual(action_base._transfer_data('/path/to/remote/file', dict(some_key=u'some value')), '/path/to/remote/file')
- self.assertEqual(action_base._transfer_data('/path/to/remote/file', dict(some_key=u'fö〩')), '/path/to/remote/file')
+ self.assertEqual(action_base._transfer_data('/path/to/remote/file', dict(some_key='some value')), '/path/to/remote/file')
+ self.assertEqual(action_base._transfer_data('/path/to/remote/file', dict(some_key='fö〩')), '/path/to/remote/file')
mock_afo.write.side_effect = Exception()
self.assertRaises(AnsibleError, action_base._transfer_data, '/path/to/remote/file', '')
@@ -367,19 +606,19 @@ class TestActionBase(unittest.TestCase):
def test_action_base_sudo_only_if_user_differs(self):
play_context = PlayContext()
- action_base = self.DerivedActionBase(None, None, play_context, None, None, None)
- action_base._connection = Mock(exec_command=Mock(return_value=(0, '', '')))
+ action_base = DerivedActionBase(None, None, play_context, None, None, None)
+ action_base._connection = MagicMock(exec_command=MagicMock(return_value=(0, '', '')))
play_context.become = True
play_context.become_user = play_context.remote_user = 'root'
- play_context.make_become_cmd = Mock(return_value='CMD')
+ play_context.make_become_cmd = MagicMock(return_value='CMD')
action_base._low_level_execute_command('ECHO', sudoable=True)
play_context.make_become_cmd.assert_not_called()
play_context.remote_user = 'apo'
- action_base._low_level_execute_command('ECHO', sudoable=True)
- play_context.make_become_cmd.assert_called_once_with("ECHO", executable='/bin/sh')
+ action_base._low_level_execute_command('ECHO', sudoable=True, executable='/bin/csh')
+ play_context.make_become_cmd.assert_called_once_with("ECHO", executable='/bin/csh')
play_context.make_become_cmd.reset_mock()
@@ -388,6 +627,6 @@ class TestActionBase(unittest.TestCase):
try:
play_context.remote_user = 'root'
action_base._low_level_execute_command('ECHO SAME', sudoable=True)
- play_context.make_become_cmd.assert_called_once_with("ECHO SAME", executable='/bin/sh')
+ play_context.make_become_cmd.assert_called_once_with("ECHO SAME", executable=None)
finally:
C.BECOME_ALLOW_SAME_USER = become_allow_same_user
diff --git a/test/units/plugins/callback/test_callback.py b/test/units/plugins/callback/test_callback.py
new file mode 100644
index 00000000000..54964ac9df2
--- /dev/null
+++ b/test/units/plugins/callback/test_callback.py
@@ -0,0 +1,82 @@
+# (c) 2012-2014, Chris Meyers
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from six import PY3
+from copy import deepcopy
+
+from ansible.compat.tests import unittest
+from ansible.compat.tests.mock import patch, mock_open
+
+from ansible.plugins.callback import CallbackBase
+import ansible.plugins.callback as callish
+
+class TestCopyResultExclude(unittest.TestCase):
+ def setUp(self):
+ class DummyClass():
+ def __init__(self):
+ self.bar = [ 1, 2, 3 ]
+ self.a = {
+ "b": 2,
+ "c": 3,
+ }
+ self.b = {
+ "c": 3,
+ "d": 4,
+ }
+ self.foo = DummyClass()
+ self.cb = CallbackBase()
+
+ def tearDown(self):
+ pass
+
+ def test_copy_logic(self):
+ res = self.cb._copy_result_exclude(self.foo, ())
+ self.assertEqual(self.foo.bar, res.bar)
+
+ def test_copy_deep(self):
+ res = self.cb._copy_result_exclude(self.foo, ())
+ self.assertNotEqual(id(self.foo.bar), id(res.bar))
+
+ def test_no_exclude(self):
+ res = self.cb._copy_result_exclude(self.foo, ())
+ self.assertEqual(self.foo.bar, res.bar)
+ self.assertEqual(self.foo.a, res.a)
+ self.assertEqual(self.foo.b, res.b)
+
+ def test_exclude(self):
+ res = self.cb._copy_result_exclude(self.foo, ['bar', 'b'])
+ self.assertIsNone(res.bar)
+ self.assertIsNone(res.b)
+ self.assertEqual(self.foo.a, res.a)
+
+ def test_result_unmodified(self):
+ bar_id = id(self.foo.bar)
+ a_id = id(self.foo.a)
+ res = self.cb._copy_result_exclude(self.foo, ['bar', 'a'])
+
+ self.assertEqual(self.foo.bar, [ 1, 2, 3 ])
+ self.assertEqual(bar_id, id(self.foo.bar))
+
+ self.assertEqual(self.foo.a, dict(b=2, c=3))
+ self.assertEqual(a_id, id(self.foo.a))
+
+ self.assertRaises(AttributeError, self.cb._copy_result_exclude, self.foo, ['a', 'c', 'bar'])
+
diff --git a/test/units/plugins/connections/test_connection.py b/test/units/plugins/connections/test_connection.py
index 10fa44216dc..370768891d5 100644
--- a/test/units/plugins/connections/test_connection.py
+++ b/test/units/plugins/connections/test_connection.py
@@ -19,7 +19,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from six import StringIO
+from io import StringIO
from ansible.compat.tests import unittest
from ansible.playbook.play_context import PlayContext
diff --git a/test/units/plugins/connections/test_connection_ssh.py b/test/units/plugins/connections/test_connection_ssh.py
new file mode 100644
index 00000000000..039be410527
--- /dev/null
+++ b/test/units/plugins/connections/test_connection_ssh.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# (c) 2015, Toshio Kuratomi
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pipes
+import sys
+from io import StringIO
+
+from ansible.compat.tests import unittest
+from ansible.compat.tests.mock import patch, MagicMock, mock_open
+
+from ansible import constants as C
+from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
+from ansible.playbook.play_context import PlayContext
+from ansible.plugins.connection import ssh
+from ansible.utils.unicode import to_bytes, to_unicode
+
+class TestConnectionBaseClass(unittest.TestCase):
+
+ def test_plugins_connection_ssh_basic(self):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = ssh.Connection(pc, new_stdin)
+
+ # connect just returns self, so assert that
+ res = conn._connect()
+ self.assertEqual(conn, res)
+
+ ssh.SSHPASS_AVAILABLE = False
+ self.assertFalse(conn._sshpass_available())
+
+ ssh.SSHPASS_AVAILABLE = True
+ self.assertTrue(conn._sshpass_available())
+
+ with patch('subprocess.Popen') as p:
+ ssh.SSHPASS_AVAILABLE = None
+ p.return_value = MagicMock()
+ self.assertTrue(conn._sshpass_available())
+
+ ssh.SSHPASS_AVAILABLE = None
+ p.return_value = None
+ p.side_effect = OSError()
+ self.assertFalse(conn._sshpass_available())
+
+ conn.close()
+ self.assertFalse(conn._connected)
+
+ def test_plugins_connection_ssh__build_command(self):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = ssh.Connection(pc, new_stdin)
+ conn._build_command('ssh')
+
+ def test_plugins_connection_ssh__exec_command(self):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = ssh.Connection(pc, new_stdin)
+
+ conn._build_command = MagicMock()
+ conn._build_command.return_value = 'ssh something something'
+ conn._run = MagicMock()
+ conn._run.return_value = (0, 'stdout', 'stderr')
+
+ res, stdout, stderr = conn._exec_command('ssh')
+ res, stdout, stderr = conn._exec_command('ssh', 'this is some data')
+
+ @patch('select.select')
+ @patch('fcntl.fcntl')
+ @patch('os.write')
+ @patch('os.close')
+ @patch('pty.openpty')
+ @patch('subprocess.Popen')
+ def test_plugins_connection_ssh__run(self, mock_Popen, mock_openpty, mock_osclose, mock_oswrite, mock_fcntl, mock_select):
+ pc = PlayContext()
+ new_stdin = StringIO()
+
+ conn = ssh.Connection(pc, new_stdin)
+ conn._send_initial_data = MagicMock()
+ conn._examine_output = MagicMock()
+ conn._terminate_process = MagicMock()
+ conn.sshpass_pipe = [MagicMock(), MagicMock()]
+
+ mock_popen_res = MagicMock()
+ mock_popen_res.poll = MagicMock()
+ mock_popen_res.wait = MagicMock()
+ mock_popen_res.stdin = MagicMock()
+ mock_popen_res.stdin.fileno.return_value = 1000
+ mock_popen_res.stdout = MagicMock()
+ mock_popen_res.stdout.fileno.return_value = 1001
+ mock_popen_res.stderr = MagicMock()
+ mock_popen_res.stderr.fileno.return_value = 1002
+ mock_popen_res.return_code = 0
+ mock_Popen.return_value = mock_popen_res
+
+ def _mock_select(rlist, wlist, elist, timeout=None):
+ rvals = []
+ if mock_popen_res.stdin in rlist:
+ rvals.append(mock_popen_res.stdin)
+ if mock_popen_res.stderr in rlist:
+ rvals.append(mock_popen_res.stderr)
+ return (rvals, [], [])
+
+ mock_select.side_effect = _mock_select
+
+ mock_popen_res.stdout.read.side_effect = ["some data", ""]
+ mock_popen_res.stderr.read.side_effect = [""]
+ conn._run("ssh", "this is input data")
+
+ # test with a password set to trigger the sshpass write
+ pc.password = '12345'
+ mock_popen_res.stdout.read.side_effect = ["some data", "", ""]
+ mock_popen_res.stderr.read.side_effect = [""]
+ conn._run(["ssh", "is", "a", "cmd"], "this is more data")
+
+ # test with password prompting enabled
+ pc.password = None
+ pc.prompt = True
+ mock_popen_res.stdout.read.side_effect = ["some data", "", ""]
+ mock_popen_res.stderr.read.side_effect = [""]
+ conn._run("ssh", "this is input data")
+
+ # test with some become settings
+ pc.prompt = False
+ pc.become = True
+ pc.success_key = 'BECOME-SUCCESS-abcdefg'
+ mock_popen_res.stdout.read.side_effect = ["some data", "", ""]
+ mock_popen_res.stderr.read.side_effect = [""]
+ conn._run("ssh", "this is input data")
+
+ # simulate no data input
+ mock_openpty.return_value = (98, 99)
+ mock_popen_res.stdout.read.side_effect = ["some data", "", ""]
+ mock_popen_res.stderr.read.side_effect = [""]
+ conn._run("ssh", "")
+
+ # simulate no data input but Popen using new pty's fails
+ mock_Popen.return_value = None
+ mock_Popen.side_effect = [OSError(), mock_popen_res]
+ mock_popen_res.stdout.read.side_effect = ["some data", "", ""]
+ mock_popen_res.stderr.read.side_effect = [""]
+ conn._run("ssh", "")
+
+ def test_plugins_connection_ssh__examine_output(self):
+ pc = PlayContext()
+ new_stdin = StringIO()
+
+ conn = ssh.Connection(pc, new_stdin)
+
+ conn.check_password_prompt = MagicMock()
+ conn.check_become_success = MagicMock()
+ conn.check_incorrect_password = MagicMock()
+ conn.check_missing_password = MagicMock()
+
+ def _check_password_prompt(line):
+ if 'foo' in line:
+ return True
+ return False
+
+ def _check_become_success(line):
+ if 'BECOME-SUCCESS-abcdefghijklmnopqrstuvxyz' in line:
+ return True
+ return False
+
+ def _check_incorrect_password(line):
+ if 'incorrect password' in line:
+ return True
+ return False
+
+ def _check_missing_password(line):
+ if 'bad password' in line:
+ return True
+ return False
+
+ conn.check_password_prompt.side_effect = _check_password_prompt
+ conn.check_become_success.side_effect = _check_become_success
+ conn.check_incorrect_password.side_effect = _check_incorrect_password
+ conn.check_missing_password.side_effect = _check_missing_password
+
+ # test examining output for prompt
+ conn._flags = dict(
+ become_prompt = False,
+ become_success = False,
+ become_error = False,
+ become_nopasswd_error = False,
+ )
+
+ pc.prompt = True
+ output, unprocessed = conn._examine_output('source', 'state', 'line 1\nline 2\nfoo\nline 3\nthis should be the remainder', False)
+ self.assertEqual(output, 'line 1\nline 2\nline 3\n')
+ self.assertEqual(unprocessed, 'this should be the remainder')
+ self.assertTrue(conn._flags['become_prompt'])
+ self.assertFalse(conn._flags['become_success'])
+ self.assertFalse(conn._flags['become_error'])
+ self.assertFalse(conn._flags['become_nopasswd_error'])
+
+ # test examining output for become prompt
+ conn._flags = dict(
+ become_prompt = False,
+ become_success = False,
+ become_error = False,
+ become_nopasswd_error = False,
+ )
+
+ pc.prompt = False
+ pc.success_key = 'BECOME-SUCCESS-abcdefghijklmnopqrstuvxyz'
+ output, unprocessed = conn._examine_output('source', 'state', 'line 1\nline 2\nBECOME-SUCCESS-abcdefghijklmnopqrstuvxyz\nline 3\n', False)
+ self.assertEqual(output, 'line 1\nline 2\nline 3\n')
+ self.assertEqual(unprocessed, '')
+ self.assertFalse(conn._flags['become_prompt'])
+ self.assertTrue(conn._flags['become_success'])
+ self.assertFalse(conn._flags['become_error'])
+ self.assertFalse(conn._flags['become_nopasswd_error'])
+
+ # test examining output for become failure
+ conn._flags = dict(
+ become_prompt = False,
+ become_success = False,
+ become_error = False,
+ become_nopasswd_error = False,
+ )
+
+ pc.prompt = False
+ pc.success_key = None
+ output, unprocessed = conn._examine_output('source', 'state', 'line 1\nline 2\nincorrect password\n', True)
+ self.assertEqual(output, 'line 1\nline 2\nincorrect password\n')
+ self.assertEqual(unprocessed, '')
+ self.assertFalse(conn._flags['become_prompt'])
+ self.assertFalse(conn._flags['become_success'])
+ self.assertTrue(conn._flags['become_error'])
+ self.assertFalse(conn._flags['become_nopasswd_error'])
+
+ # test examining output for missing password
+ conn._flags = dict(
+ become_prompt = False,
+ become_success = False,
+ become_error = False,
+ become_nopasswd_error = False,
+ )
+
+ pc.prompt = False
+ pc.success_key = None
+ output, unprocessed = conn._examine_output('source', 'state', 'line 1\nbad password\n', True)
+ self.assertEqual(output, 'line 1\nbad password\n')
+ self.assertEqual(unprocessed, '')
+ self.assertFalse(conn._flags['become_prompt'])
+ self.assertFalse(conn._flags['become_success'])
+ self.assertFalse(conn._flags['become_error'])
+ self.assertTrue(conn._flags['become_nopasswd_error'])
+
+ @patch('time.sleep')
+ def test_plugins_connection_ssh_exec_command(self, mock_sleep):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = ssh.Connection(pc, new_stdin)
+ conn._build_command = MagicMock()
+ conn._exec_command = MagicMock()
+
+ C.ANSIBLE_SSH_RETRIES = 9
+
+ # test a regular, successful execution
+ conn._exec_command.return_value = (0, 'stdout', '')
+ res = conn.exec_command('ssh', 'some data')
+
+ # test a retry, followed by success
+ conn._exec_command.return_value = None
+ conn._exec_command.side_effect = [(255, '', ''), (0, 'stdout', '')]
+ res = conn.exec_command('ssh', 'some data')
+
+ # test multiple failures
+ conn._exec_command.side_effect = [(255, '', '')]*10
+ self.assertRaises(AnsibleConnectionFailure, conn.exec_command, 'ssh', 'some data')
+
+ # test other failure from exec_command
+ conn._exec_command.side_effect = [Exception('bad')]*10
+ self.assertRaises(Exception, conn.exec_command, 'ssh', 'some data')
+
+ @patch('os.path.exists')
+ def test_plugins_connection_ssh_put_file(self, mock_ospe):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = ssh.Connection(pc, new_stdin)
+ conn._build_command = MagicMock()
+ conn._run = MagicMock()
+
+ mock_ospe.return_value = True
+ conn._build_command.return_value = 'some command to run'
+ conn._run.return_value = (0, '', '')
+ conn.host = "some_host"
+
+ # test with C.DEFAULT_SCP_IF_SSH enabled
+ C.DEFAULT_SCP_IF_SSH = True
+ res = conn.put_file('/path/to/in/file', '/path/to/dest/file')
+ conn._run.assert_called_with('some command to run', None)
+
+ res = conn.put_file(u'/path/to/in/file/with/unicode-fö〩', u'/path/to/dest/file/with/unicode-fö〩')
+ conn._run.assert_called_with('some command to run', None)
+
+ # test with C.DEFAULT_SCP_IF_SSH disabled
+ C.DEFAULT_SCP_IF_SSH = False
+ expected_in_data = b' '.join((b'put', to_bytes(pipes.quote('/path/to/in/file')), to_bytes(pipes.quote('/path/to/dest/file')))) + b'\n'
+ res = conn.put_file('/path/to/in/file', '/path/to/dest/file')
+ conn._run.assert_called_with('some command to run', expected_in_data)
+
+ expected_in_data = b' '.join((b'put', to_bytes(pipes.quote('/path/to/in/file/with/unicode-fö〩')), to_bytes(pipes.quote('/path/to/dest/file/with/unicode-fö〩')))) + b'\n'
+ res = conn.put_file(u'/path/to/in/file/with/unicode-fö〩', u'/path/to/dest/file/with/unicode-fö〩')
+ conn._run.assert_called_with('some command to run', expected_in_data)
+
+ # test that a non-zero rc raises an error
+ conn._run.return_value = (1, 'stdout', 'some errors')
+ self.assertRaises(AnsibleError, conn.put_file, '/path/to/bad/file', '/remote/path/to/file')
+
+ # test that a not-found path raises an error
+ mock_ospe.return_value = False
+ conn._run.return_value = (0, 'stdout', '')
+ self.assertRaises(AnsibleFileNotFound, conn.put_file, '/path/to/bad/file', '/remote/path/to/file')
+
+ def test_plugins_connection_ssh_fetch_file(self):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = ssh.Connection(pc, new_stdin)
+ conn._build_command = MagicMock()
+ conn._run = MagicMock()
+
+ conn._build_command.return_value = 'some command to run'
+ conn._run.return_value = (0, '', '')
+ conn.host = "some_host"
+
+ # test with C.DEFAULT_SCP_IF_SSH enabled
+ C.DEFAULT_SCP_IF_SSH = True
+ res = conn.fetch_file('/path/to/in/file', '/path/to/dest/file')
+ conn._run.assert_called_with('some command to run', None)
+
+ res = conn.fetch_file(u'/path/to/in/file/with/unicode-fö〩', u'/path/to/dest/file/with/unicode-fö〩')
+ conn._run.assert_called_with('some command to run', None)
+
+ # test with C.DEFAULT_SCP_IF_SSH disabled
+ C.DEFAULT_SCP_IF_SSH = False
+ expected_in_data = b' '.join((b'get', to_bytes(pipes.quote('/path/to/in/file')), to_bytes(pipes.quote('/path/to/dest/file')))) + b'\n'
+ res = conn.fetch_file('/path/to/in/file', '/path/to/dest/file')
+ conn._run.assert_called_with('some command to run', expected_in_data)
+
+ expected_in_data = b' '.join((b'get', to_bytes(pipes.quote('/path/to/in/file/with/unicode-fö〩')), to_bytes(pipes.quote('/path/to/dest/file/with/unicode-fö〩')))) + b'\n'
+ res = conn.fetch_file(u'/path/to/in/file/with/unicode-fö〩', u'/path/to/dest/file/with/unicode-fö〩')
+ conn._run.assert_called_with('some command to run', expected_in_data)
+
+ # test that a non-zero rc raises an error
+ conn._run.return_value = (1, 'stdout', 'some errors')
+ self.assertRaises(AnsibleError, conn.fetch_file, '/path/to/bad/file', '/remote/path/to/file')
+
diff --git a/test/units/plugins/strategies/test_strategy_base.py b/test/units/plugins/strategies/test_strategy_base.py
index da14f6887aa..9ea944a2a17 100644
--- a/test/units/plugins/strategies/test_strategy_base.py
+++ b/test/units/plugins/strategies/test_strategy_base.py
@@ -76,6 +76,7 @@ class TestStrategyBase(unittest.TestCase):
for i in range(0, 5):
mock_host = MagicMock()
mock_host.name = "host%02d" % (i+1)
+ mock_host.has_hostkey = True
mock_hosts.append(mock_host)
mock_inventory = MagicMock()
@@ -111,6 +112,7 @@ class TestStrategyBase(unittest.TestCase):
fake_loader = DictDataLoader()
mock_var_manager = MagicMock()
mock_host = MagicMock()
+ mock_host.has_hostkey = True
mock_inventory = MagicMock()
mock_options = MagicMock()
mock_options.module_path = None
@@ -171,6 +173,7 @@ class TestStrategyBase(unittest.TestCase):
mock_host = MagicMock()
mock_host.name = 'test01'
mock_host.vars = dict()
+ mock_host.has_hostkey = True
mock_task = MagicMock()
mock_task._role = None
@@ -348,6 +351,7 @@ class TestStrategyBase(unittest.TestCase):
mock_host = MagicMock(Host)
mock_host.name = "test01"
+ mock_host.has_hostkey = True
mock_inventory = MagicMock()
mock_inventory.get_hosts.return_value = [mock_host]
diff --git a/test/utils/ansible-playbook_integration_runner/main.yml b/test/utils/ansible-playbook_integration_runner/main.yml
index f1bd26b7ead..82ec9ec9bf6 100644
--- a/test/utils/ansible-playbook_integration_runner/main.yml
+++ b/test/utils/ansible-playbook_integration_runner/main.yml
@@ -67,7 +67,7 @@
region: 'us-east-1'
instance_ids: "{{ hostvars[item]['ec2_instance_ids'] }}"
when: hostvars[item]['ec2_instance_ids'] is defined and item == inventory_hostname
- with_items: groups['dynamic_hosts']
+ with_items: "{{groups['dynamic_hosts']}}"
- set_fact:
ansible_connection: local
diff --git a/test/utils/docker/centos6/Dockerfile b/test/utils/docker/centos6/Dockerfile
new file mode 100644
index 00000000000..dd53b6e2efc
--- /dev/null
+++ b/test/utils/docker/centos6/Dockerfile
@@ -0,0 +1,45 @@
+# Latest version of centos
+FROM centos:centos6
+RUN yum -y update; yum clean all;
+RUN yum -y install \
+ epel-release \
+ file \
+ gcc \
+ git \
+ make \
+ mercurial \
+ rubygems \
+ sed \
+ subversion \
+ sudo \
+ unzip \
+ openssh-clients \
+ openssh-server \
+ which
+RUN yum -y install \
+ PyYAML \
+ python-coverage \
+ python-devel \
+ python-httplib2 \
+ python-jinja2 \
+ python-keyczar \
+ python-mock \
+ python-nose \
+ python-paramiko \
+ python-pip \
+ python-setuptools \
+ python-virtualenv
+RUN pip install --upgrade jinja2
+RUN rpm -e --nodeps python-crypto; pip install pycrypto
+RUN /bin/sed -i -e 's/^\(Defaults\s*requiretty\)/#--- \1/' /etc/sudoers
+RUN mkdir /etc/ansible/
+RUN /bin/echo -e '[local]\nlocalhost ansible_connection=local' > /etc/ansible/hosts
+#VOLUME /sys/fs/cgroup /run /tmp
+RUN ssh-keygen -q -t rsa1 -N '' -f /etc/ssh/ssh_host_key && \
+ ssh-keygen -q -t dsa -N '' -f /etc/ssh/ssh_host_dsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /etc/ssh/ssh_host_rsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /root/.ssh/id_rsa && \
+ cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys && \
+ for key in /etc/ssh/ssh_host_*_key.pub; do echo "localhost $(cat ${key})" >> /root/.ssh/known_hosts; done
+ENV container=docker
+CMD ["/sbin/init"]
diff --git a/test/utils/docker/centos7/Dockerfile b/test/utils/docker/centos7/Dockerfile
new file mode 100644
index 00000000000..a23707ef582
--- /dev/null
+++ b/test/utils/docker/centos7/Dockerfile
@@ -0,0 +1,50 @@
+# Latest version of centos
+FROM centos:centos7
+RUN yum -y update; yum clean all; yum -y swap fakesystemd systemd
+RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
+rm -f /lib/systemd/system/multi-user.target.wants/*; \
+rm -f /etc/systemd/system/*.wants/*; \
+rm -f /lib/systemd/system/local-fs.target.wants/*; \
+rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
+rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
+rm -f /lib/systemd/system/basic.target.wants/*; \
+rm -f /lib/systemd/system/anaconda.target.wants/*;
+RUN yum -y install \
+ dbus-python \
+ epel-release \
+ file \
+ git \
+ iproute \
+ make \
+ mercurial \
+ rubygems \
+ subversion \
+ sudo \
+ unzip \
+ openssh-clients \
+ openssh-server \
+ which
+RUN yum -y install \
+ PyYAML \
+ python-coverage \
+ python-httplib2 \
+ python-jinja2 \
+ python-keyczar \
+ python-mock \
+ python-nose \
+ python-paramiko \
+ python-pip \
+ python-setuptools \
+ python-virtualenv
+RUN /usr/bin/sed -i -e 's/^\(Defaults\s*requiretty\)/#--- \1/' /etc/sudoers
+RUN mkdir /etc/ansible/
+RUN /usr/bin/echo -e '[local]\nlocalhost ansible_connection=local' > /etc/ansible/hosts
+VOLUME /sys/fs/cgroup /run /tmp
+RUN ssh-keygen -q -t rsa1 -N '' -f /etc/ssh/ssh_host_key && \
+ ssh-keygen -q -t dsa -N '' -f /etc/ssh/ssh_host_dsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /etc/ssh/ssh_host_rsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /root/.ssh/id_rsa && \
+ cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys && \
+ for key in /etc/ssh/ssh_host_*_key.pub; do echo "localhost $(cat ${key})" >> /root/.ssh/known_hosts; done
+ENV container=docker
+CMD ["/usr/sbin/init"]
diff --git a/test/utils/docker/fedora-rawhide/Dockerfile b/test/utils/docker/fedora-rawhide/Dockerfile
new file mode 100644
index 00000000000..7587b93ab46
--- /dev/null
+++ b/test/utils/docker/fedora-rawhide/Dockerfile
@@ -0,0 +1,54 @@
+# Latest version of fedora rawhide
+FROM fedora:rawhide
+RUN dnf -y update; dnf clean all
+RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
+rm -f /lib/systemd/system/multi-user.target.wants/*; \
+rm -f /etc/systemd/system/*.wants/*; \
+rm -f /lib/systemd/system/local-fs.target.wants/*; \
+rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
+rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
+rm -f /lib/systemd/system/basic.target.wants/*; \
+rm -f /lib/systemd/system/anaconda.target.wants/*;
+RUN dnf -y install \
+ dbus-python \
+ file \
+ findutils \
+ git \
+ glibc-locale-source \
+ iproute \
+ make \
+ mercurial \
+ procps \
+ PyYAML \
+ python-coverage \
+ python2-dnf \
+ python-httplib2 \
+ python-jinja2 \
+ python-keyczar \
+ python-mock \
+ python-nose \
+ python-paramiko \
+ python-pip \
+ python-setuptools \
+ python-virtualenv \
+ rubygems \
+ subversion \
+ sudo \
+ tar \
+ unzip \
+ which \
+ openssh-clients \
+ openssh-server \
+ yum
+RUN localedef --quiet -c -i en_US -f UTF-8 en_US.UTF-8
+RUN /usr/bin/sed -i -e 's/^\(Defaults\s*requiretty\)/#--- \1/' /etc/sudoers
+RUN mkdir /etc/ansible/
+RUN /usr/bin/echo -e '[local]\nlocalhost ansible_connection=local' > /etc/ansible/hosts
+VOLUME /sys/fs/cgroup /run /tmp
+RUN ssh-keygen -q -t dsa -N '' -f /etc/ssh/ssh_host_dsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /etc/ssh/ssh_host_rsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /root/.ssh/id_rsa && \
+ cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys && \
+ for key in /etc/ssh/ssh_host_*_key.pub; do echo "localhost $(cat ${key})" >> /root/.ssh/known_hosts; done
+ENV container=docker
+CMD ["/usr/sbin/init"]
diff --git a/test/utils/docker/fedora23/Dockerfile b/test/utils/docker/fedora23/Dockerfile
new file mode 100644
index 00000000000..a0563743878
--- /dev/null
+++ b/test/utils/docker/fedora23/Dockerfile
@@ -0,0 +1,55 @@
+# Latest version of centos
+FROM fedora:23
+RUN dnf -y update; dnf clean all
+RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
+rm -f /lib/systemd/system/multi-user.target.wants/*; \
+rm -f /etc/systemd/system/*.wants/*; \
+rm -f /lib/systemd/system/local-fs.target.wants/*; \
+rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
+rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
+rm -f /lib/systemd/system/basic.target.wants/*; \
+rm -f /lib/systemd/system/anaconda.target.wants/*;
+RUN dnf -y install \
+ dbus-python \
+ file \
+ findutils \
+ glibc-common \
+ git \
+ iproute \
+ make \
+ mercurial \
+ procps \
+ PyYAML \
+ python-coverage \
+ python2-dnf \
+ python-httplib2 \
+ python-jinja2 \
+ python-keyczar \
+ python-mock \
+ python-nose \
+ python-paramiko \
+ python-pip \
+ python-setuptools \
+ python-virtualenv \
+ rubygems \
+ subversion \
+ sudo \
+ tar \
+ unzip \
+ which \
+ openssh-clients \
+ openssh-server \
+ yum
+RUN localedef --quiet -f ISO-8859-1 -i pt_BR pt_BR
+RUN localedef --quiet -f ISO-8859-1 -i es_MX es_MX
+RUN /usr/bin/sed -i -e 's/^\(Defaults\s*requiretty\)/#--- \1/' /etc/sudoers
+RUN mkdir /etc/ansible/
+RUN /usr/bin/echo -e '[local]\nlocalhost ansible_connection=local' > /etc/ansible/hosts
+VOLUME /sys/fs/cgroup /run /tmp
+RUN ssh-keygen -q -t dsa -N '' -f /etc/ssh/ssh_host_dsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /etc/ssh/ssh_host_rsa_key && \
+ ssh-keygen -q -t rsa -N '' -f /root/.ssh/id_rsa && \
+ cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys && \
+ for key in /etc/ssh/ssh_host_*_key.pub; do echo "localhost $(cat ${key})" >> /root/.ssh/known_hosts; done
+ENV container=docker
+CMD ["/usr/sbin/init"]
diff --git a/test/utils/docker/ubuntu1204/Dockerfile b/test/utils/docker/ubuntu1204/Dockerfile
new file mode 100644
index 00000000000..6924bba7032
--- /dev/null
+++ b/test/utils/docker/ubuntu1204/Dockerfile
@@ -0,0 +1,69 @@
+FROM ubuntu:precise
+RUN apt-get clean; apt-get update -y;
+RUN apt-get install -y \
+ debianutils \
+ gawk \
+ git \
+ locales \
+ make \
+ mercurial \
+ ruby \
+ rubygems \
+ subversion \
+ sudo \
+ openssh-client \
+ openssh-server \
+ unzip
+
+# helpful things taken from the ubuntu-upstart Dockerfile:
+# https://github.com/tianon/dockerfiles/blob/4d24a12b54b75b3e0904d8a285900d88d3326361/sbin-init/ubuntu/upstart/14.04/Dockerfile
+ADD init-fake.conf /etc/init/fake-container-events.conf
+
+# undo some leet hax of the base image
+RUN rm /usr/sbin/policy-rc.d; \
+ rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
+# remove some pointless services
+RUN /usr/sbin/update-rc.d -f ondemand remove; \
+ for f in \
+ /etc/init/u*.conf \
+ /etc/init/mounted-dev.conf \
+ /etc/init/mounted-proc.conf \
+ /etc/init/mounted-run.conf \
+ /etc/init/mounted-tmp.conf \
+ /etc/init/mounted-var.conf \
+ /etc/init/hostname.conf \
+ /etc/init/networking.conf \
+ /etc/init/tty*.conf \
+ /etc/init/plymouth*.conf \
+ /etc/init/hwclock*.conf \
+ /etc/init/module*.conf\
+ ; do \
+ dpkg-divert --local --rename --add "$f"; \
+ done; \
+ echo '# /lib/init/fstab: cleared out for bare-bones Docker' > /lib/init/fstab
+# end things from ubuntu-upstart Dockerfile
+
+RUN apt-get install -y \
+ python-coverage \
+ python-dev \
+ python-httplib2 \
+ python-jinja2 \
+ python-keyczar \
+ python-mock \
+ python-nose \
+ python-paramiko \
+ python-pip \
+ python-setuptools \
+ python-virtualenv \
+ python-yaml
+RUN pip install --upgrade jinja2 pycrypto
+RUN rm /etc/apt/apt.conf.d/docker-clean
+RUN /bin/sed -i -e 's/^\(Defaults\s*requiretty\)/#--- \1/' /etc/sudoers
+RUN mkdir /etc/ansible/
+RUN /bin/echo -e "[local]\nlocalhost ansible_connection=local" > /etc/ansible/hosts
+RUN locale-gen en_US.UTF-8
+RUN ssh-keygen -q -t rsa -N '' -f /root/.ssh/id_rsa && \
+ cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys && \
+ for key in /etc/ssh/ssh_host_*_key.pub; do echo "localhost $(cat ${key})" >> /root/.ssh/known_hosts; done
+ENV container docker
+CMD ["/sbin/init"]
diff --git a/test/utils/docker/ubuntu1204/init-fake.conf b/test/utils/docker/ubuntu1204/init-fake.conf
new file mode 100644
index 00000000000..f5db965051e
--- /dev/null
+++ b/test/utils/docker/ubuntu1204/init-fake.conf
@@ -0,0 +1,13 @@
+# fake some events needed for correct startup other services
+
+description "In-Container Upstart Fake Events"
+
+start on startup
+
+script
+ rm -rf /var/run/*.pid
+ rm -rf /var/run/network/*
+ /sbin/initctl emit stopped JOB=udevtrigger --no-wait
+ /sbin/initctl emit started JOB=udev --no-wait
+ /sbin/initctl emit runlevel RUNLEVEL=3 --no-wait
+end script
diff --git a/test/utils/docker/ubuntu1404/Dockerfile b/test/utils/docker/ubuntu1404/Dockerfile
new file mode 100644
index 00000000000..51d24c5e03c
--- /dev/null
+++ b/test/utils/docker/ubuntu1404/Dockerfile
@@ -0,0 +1,66 @@
+FROM ubuntu:trusty
+RUN apt-get clean; apt-get update -y;
+RUN apt-get install -y \
+ debianutils \
+ gawk \
+ git \
+ locales \
+ make \
+ mercurial \
+ ruby \
+ subversion \
+ sudo \
+ openssh-client \
+ openssh-server \
+ unzip
+
+# helpful things taken from the ubuntu-upstart Dockerfile:
+# https://github.com/tianon/dockerfiles/blob/4d24a12b54b75b3e0904d8a285900d88d3326361/sbin-init/ubuntu/upstart/14.04/Dockerfile
+ADD init-fake.conf /etc/init/fake-container-events.conf
+
+# undo some leet hax of the base image
+RUN rm /usr/sbin/policy-rc.d; \
+ rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
+# remove some pointless services
+RUN /usr/sbin/update-rc.d -f ondemand remove; \
+ for f in \
+ /etc/init/u*.conf \
+ /etc/init/mounted-dev.conf \
+ /etc/init/mounted-proc.conf \
+ /etc/init/mounted-run.conf \
+ /etc/init/mounted-tmp.conf \
+ /etc/init/mounted-var.conf \
+ /etc/init/hostname.conf \
+ /etc/init/networking.conf \
+ /etc/init/tty*.conf \
+ /etc/init/plymouth*.conf \
+ /etc/init/hwclock*.conf \
+ /etc/init/module*.conf\
+ ; do \
+ dpkg-divert --local --rename --add "$f"; \
+ done; \
+ echo '# /lib/init/fstab: cleared out for bare-bones Docker' > /lib/init/fstab
+# end things from ubuntu-upstart Dockerfile
+
+RUN apt-get install -y \
+ python-coverage \
+ python-httplib2 \
+ python-jinja2 \
+ python-keyczar \
+ python-mock \
+ python-nose \
+ python-paramiko \
+ python-pip \
+ python-setuptools \
+ python-virtualenv \
+ python-yaml
+RUN rm /etc/apt/apt.conf.d/docker-clean
+RUN /bin/sed -i -e 's/^\(Defaults\s*requiretty\)/#--- \1/' /etc/sudoers
+RUN mkdir /etc/ansible/
+RUN /bin/echo -e "[local]\nlocalhost ansible_connection=local" > /etc/ansible/hosts
+RUN locale-gen en_US.UTF-8
+RUN ssh-keygen -q -t rsa -N '' -f /root/.ssh/id_rsa && \
+ cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys && \
+ for key in /etc/ssh/ssh_host_*_key.pub; do echo "localhost $(cat ${key})" >> /root/.ssh/known_hosts; done
+ENV container docker
+CMD ["/sbin/init"]
diff --git a/test/utils/docker/ubuntu1404/init-fake.conf b/test/utils/docker/ubuntu1404/init-fake.conf
new file mode 100644
index 00000000000..f5db965051e
--- /dev/null
+++ b/test/utils/docker/ubuntu1404/init-fake.conf
@@ -0,0 +1,13 @@
+# fake some events needed for correct startup other services
+
+description "In-Container Upstart Fake Events"
+
+start on startup
+
+script
+ rm -rf /var/run/*.pid
+ rm -rf /var/run/network/*
+ /sbin/initctl emit stopped JOB=udevtrigger --no-wait
+ /sbin/initctl emit started JOB=udev --no-wait
+ /sbin/initctl emit runlevel RUNLEVEL=3 --no-wait
+end script
diff --git a/test/utils/run_tests.sh b/test/utils/run_tests.sh
new file mode 100755
index 00000000000..eef7f1f60ed
--- /dev/null
+++ b/test/utils/run_tests.sh
@@ -0,0 +1,20 @@
+#!/bin/sh -x
+
+if [ "${TARGET}" = "sanity" ]; then
+ ./test/code-smell/replace-urlopen.sh .
+ ./test/code-smell/use-compat-six.sh lib
+ ./test/code-smell/boilerplate.sh
+ ./test/code-smell/required-and-default-attributes.sh
+ if test x"$TOXENV" != x'py24' ; then tox ; fi
+ if test x"$TOXENV" = x'py24' ; then python2.4 -V && python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce).py' lib/ansible/module_utils ; fi
+else
+ set -e
+ export C_NAME="testAbull_$$_$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)"
+ docker run -d --volume="${PWD}:/root/ansible:Z" --name "${C_NAME}" ${TARGET_OPTIONS} ansible/ansible:${TARGET} > /tmp/cid_${TARGET}
+ docker exec -ti $(cat /tmp/cid_${TARGET}) /bin/sh -c "export TEST_FLAGS='${TEST_FLAGS}'; cd /root/ansible; . hacking/env-setup; (cd test/integration; LC_ALL=en_US.utf-8 make)"
+ docker kill $(cat /tmp/cid_${TARGET})
+
+ if [ "X${TESTS_KEEP_CONTAINER}" = "X" ]; then
+ docker rm "${C_NAME}"
+ fi
+fi
diff --git a/test/utils/tox/requirements-py3.txt b/test/utils/tox/requirements-py3.txt
new file mode 100644
index 00000000000..1ff4fb0cb26
--- /dev/null
+++ b/test/utils/tox/requirements-py3.txt
@@ -0,0 +1,13 @@
+#
+# Test requirements
+#
+
+nose
+mock >= 1.0.1, < 1.1
+passlib
+coverage
+coveralls
+unittest2
+redis
+python3-memcached
+python-systemd
diff --git a/test/utils/tox/requirements.txt b/test/utils/tox/requirements.txt
new file mode 100644
index 00000000000..34de42bc14a
--- /dev/null
+++ b/test/utils/tox/requirements.txt
@@ -0,0 +1,13 @@
+#
+# Test requirements
+#
+
+nose
+mock >= 1.0.1, < 1.1
+passlib
+coverage
+coveralls
+unittest2
+redis
+python-memcached
+python-systemd