diff --git a/test/integration/Makefile b/test/integration/Makefile index da2758c140..d8ca0a10c2 100644 --- a/test/integration/Makefile +++ b/test/integration/Makefile @@ -39,6 +39,9 @@ cloud_cleanup: amazon_cleanup rackspace_cleanup amazon_cleanup: python cleanup_ec2.py -y --match="^$(CLOUD_RESOURCE_PREFIX)" +gce_cleanup: + python cleanup_gce.py -y --match="^$(CLOUD_RESOURCE_PREFIX)" + rackspace_cleanup: @echo "FIXME - cleanup_rax.py not yet implemented" @# python cleanup_rax.py -y --match="^$(CLOUD_RESOURCE_PREFIX)" @@ -53,6 +56,12 @@ amazon: $(CREDENTIALS_FILE) CLOUD_RESOURCE_PREFIX="$(CLOUD_RESOURCE_PREFIX)" make amazon_cleanup ; \ exit $$RC; +gce: $(CREDENTIALS_FILE) + ansible-playbook gce.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \ + RC=$$? ; \ + CLOUD_RESOURCE_PREFIX="$(CLOUD_RESOURCE_PREFIX)" make gce_cleanup ; \ + exit $$RC; + rackspace: $(CREDENTIALS_FILE) ansible-playbook rackspace.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \ RC=$$? ; \ diff --git a/test/integration/cleanup_gce.py b/test/integration/cleanup_gce.py new file mode 100644 index 0000000000..9b1645e93e --- /dev/null +++ b/test/integration/cleanup_gce.py @@ -0,0 +1,102 @@ +''' +Find and delete GCE resources matching the provided --match string. Unless +--yes|-y is provided, the prompt for confirmation prior to deleting resources. +Please use caution, you can easily delete your *ENTIRE* GCE infrastructure. +''' + +import os +import re +import sys +import optparse +import yaml + +try: + from libcloud.compute.types import Provider + from libcloud.compute.providers import get_driver + from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ + ResourceExistsError, ResourceInUseError, ResourceNotFoundError + _ = Provider.GCE +except ImportError: + print("failed=True " + \ + "msg='libcloud with GCE support (0.13.3+) required for this module'") + sys.exit(1) + + +def delete_gce_resources(get_func, attr, opts): + for item in get_func(): + val = getattr(item, attr) + if re.search(opts.match_re, val, re.IGNORECASE): + prompt_and_delete(item, "Delete matching %s? [y/n]: " % (item,), opts.assumeyes) + +def prompt_and_delete(item, prompt, assumeyes): + if not assumeyes: + assumeyes = raw_input(prompt).lower() == 'y' + assert hasattr(item, 'destroy'), "Class <%s> has no delete attribute" % item.__class__ + if assumeyes: + item.destroy() + print ("Deleted %s" % item) + +def parse_args(): + default_service_account_email = None + default_pem_file = None + default_project_id = None + + # Load details from credentials.yml + if os.path.isfile('credentials.yml'): + credentials = yaml.load(open('credentials.yml', 'r')) + + if default_service_account_email is None: + default_service_account_email = credentials['gce_service_account_email'] + if default_pem_file is None: + default_pem_file = credentials['gce_pem_file'] + if default_project_id is None: + default_project_id = credentials['gce_project_id'] + + parser = optparse.OptionParser(usage="%s [options]" % (sys.argv[0],), + description=__doc__) + parser.add_option("--service_account_email", + action="store", dest="service_account_email", + default=default_service_account_email, + help="GCE service account email. Default is loaded from credentials.yml.") + parser.add_option("--pem_file", + action="store", dest="pem_file", + default=default_pem_file, + help="GCE client key. Default is loaded from credentials.yml.") + parser.add_option("--project_id", + action="store", dest="project_id", + default=default_project_id, + help="Google Cloud project ID. Default is loaded from credentials.yml.") + parser.add_option("--credentials", "-c", + action="store", dest="credential_file", + default="credentials.yml", + help="YAML file to read cloud credentials (default: %default)") + parser.add_option("--yes", "-y", + action="store_true", dest="assumeyes", + default=False, + help="Don't prompt for confirmation") + parser.add_option("--match", + action="store", dest="match_re", + default="^ansible-testing-", + help="Regular expression used to find GCE resources (default: %default)") + + (opts, args) = parser.parse_args() + for required in ['service_account_email', 'pem_file', 'project_id']: + if getattr(opts, required) is None: + parser.error("Missing required parameter: --%s" % required) + + return (opts, args) + +if __name__ == '__main__': + + (opts, args) = parse_args() + + # Connect to GCE + gce_cls = get_driver(Provider.GCE) + gce = gce_cls( + opts.service_account_email, opts.pem_file, project=opts.project_id) + + try: + # Delete matching instances + delete_gce_resources(gce.list_nodes, 'name', opts) + except KeyboardInterrupt, e: + print "\nExiting on user command." diff --git a/test/integration/credentials.template b/test/integration/credentials.template index f21100405f..12316254bb 100644 --- a/test/integration/credentials.template +++ b/test/integration/credentials.template @@ -3,5 +3,10 @@ ec2_access_key: ec2_secret_key: +# GCE Credentials +service_account_email: +pem_file: +project_id: + # GITHUB SSH private key - a path to a SSH private key for use with github.com github_ssh_private_key: "{{ lookup('env','HOME') }}/.ssh/id_rsa" diff --git a/test/integration/gce.yml b/test/integration/gce.yml new file mode 100644 index 0000000000..75287abab1 --- /dev/null +++ b/test/integration/gce.yml @@ -0,0 +1,5 @@ +- hosts: testhost + gather_facts: true + roles: + - { role: test_gce, tags: test_gce } + # TODO: tests for gce_net, gce_pd, etc. diff --git a/test/integration/roles/test_gce/defaults/main.yml b/test/integration/roles/test_gce/defaults/main.yml new file mode 100644 index 0000000000..1564808d79 --- /dev/null +++ b/test/integration/roles/test_gce/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# defaults file for test_gce +instance_name: "{{ resource_prefix|lower }}" +service_account_email: "{{ gce_service_account_email }}" +pem_file: "{{ gce_pem_file }}" +project_id: "{{ gce_project_id }}" diff --git a/test/integration/roles/test_gce/tasks/main.yml b/test/integration/roles/test_gce/tasks/main.yml new file mode 100644 index 0000000000..8791c281a5 --- /dev/null +++ b/test/integration/roles/test_gce/tasks/main.yml @@ -0,0 +1,97 @@ +# TODO: lots of attributes not covered: machine_type, zone, metadata, tags, etc. +# +# ============================================================ +- name: test with no parameters + gce: + register: result + ignore_errors: true + +- name: assert failure when called with no parameters + assert: + that: + - 'result.failed' + - 'result.msg == "Missing GCE connection parameters in libcloud secrets file."' + +# ============================================================ +- name: test missing name + gce: + service_account_email: "{{ service_account_email }}" + pem_file: "{{ pem_file }}" + project_id: "{{ project_id }}" + register: result + ignore_errors: true + +- name: assert failure when called with no parameters + assert: + that: + - 'result.failed' + - 'result.msg == "Must specify a \"name\" or \"instance_names\""' + +# ============================================================ +- name: test state=present (expected changed=true) + gce: + name: "{{ instance_name }}" + service_account_email: "{{ service_account_email }}" + pem_file: "{{ pem_file }}" + project_id: "{{ project_id }}" + state: present + register: result + +- name: assert state=present (expected changed=true) + assert: + that: + - 'result.changed' + - 'result.name == "{{ instance_name }}"' + - 'result.state == "present"' + +# ============================================================ +- name: test state=present (expected changed=false) + gce: + name: "{{ instance_name }}" + service_account_email: "{{ service_account_email }}" + pem_file: "{{ pem_file }}" + project_id: "{{ project_id }}" + state: present + register: result + +- name: assert state=present (expected changed=false) + assert: + that: + - 'not result.changed' + - 'result.name == "{{ instance_name }}"' + - 'result.state == "present"' + +# ============================================================ +- name: test state=absent (expected changed=true) + gce: + name: "{{ instance_name }}" + service_account_email: "{{ service_account_email }}" + pem_file: "{{ pem_file }}" + project_id: "{{ project_id }}" + state: absent + register: result + +- name: assert state=absent (expected changed=true) + assert: + that: + - 'result.changed' + - 'result.name == "{{ instance_name }}"' + - 'result.state == "absent"' + +# ============================================================ +- name: test state=absent (expected changed=false) + gce: + name: "{{ instance_name }}" + service_account_email: "{{ service_account_email }}" + pem_file: "{{ pem_file }}" + project_id: "{{ project_id }}" + state: absent + register: result + +- name: assert state=absent (expected changed=false) + assert: + that: + - 'not result.changed' + - 'result.name == "{{ instance_name }}"' + - 'result.state == "absent"' +