Merge branch 'matlockx-devel' into devel

pull/7745/head
James Cammarata 11 years ago
commit b174b1a536

@ -92,6 +92,11 @@ options:
wait_timeout: wait_timeout:
description: description:
- how long before wait gives up, in seconds - how long before wait gives up, in seconds
default: 600
aliases: []
wait_timeout_redirects:
description:
- how long before wait gives up for redirects, in seconds
default: 300 default: 300
aliases: [] aliases: []
state: state:
@ -137,23 +142,59 @@ from urlparse import urlparse
AZURE_LOCATIONS = ['East Asia', AZURE_LOCATIONS = ['East Asia',
'Southeast Asia', 'Southeast Asia',
'Brazil South',
'North Europe', 'North Europe',
'West Europe', 'West Europe',
'Japan West',
'East US', 'East US',
'South Central US',
'West US'] 'West US']
AZURE_ROLE_SIZES = ['Small', 'Medium', 'Large', 'ExtraLarge', 'A5', 'A6', 'A7'] AZURE_ROLE_SIZES = ['ExtraSmall',
'Small',
'Medium',
'Large',
'ExtraLarge',
'A5',
'A6',
'A7',
'A8',
'A9',
'Basic_A0',
'Basic_A1',
'Basic_A2',
'Basic_A3',
'Basic_A4']
try: try:
import azure import azure as windows_azure
from azure import WindowsAzureError, WindowsAzureMissingResourceError from azure import WindowsAzureError, WindowsAzureMissingResourceError
from azure.servicemanagement import (ServiceManagementService, OSVirtualHardDisk, SSH, PublicKeys, from azure.servicemanagement import (ServiceManagementService, OSVirtualHardDisk, SSH, PublicKeys,
PublicKey, LinuxConfigurationSet, ConfigurationSetInputEndpoints, PublicKey, LinuxConfigurationSet, ConfigurationSetInputEndpoints,
ConfigurationSetInputEndpoint) ConfigurationSetInputEndpoint)
except ImportError: except ImportError:
print "failed=True msg='azure required for this module'" print
"failed=True msg='azure required for this module'"
sys.exit(1) sys.exit(1)
from distutils.version import LooseVersion
from types import MethodType
import json
def _wait_for_completion(azure, promise, wait_timeout, msg):
if not promise: return
wait_timeout = time.time() + wait_timeout
while wait_timeout > time.time():
operation_result = azure.get_operation_status(promise.request_id)
time.sleep(5)
if operation_result.status == "Succeeded":
return
raise WindowsAzureError('Timed out waiting for async operation ' + msg + ' "' + str(promise.request_id) + '" to complete.')
def get_ssh_certificate_tokens(module, ssh_cert_path): def get_ssh_certificate_tokens(module, ssh_cert_path):
""" """
Returns the sha1 fingerprint and a base64-encoded PKCS12 version of the certificate. Returns the sha1 fingerprint and a base64-encoded PKCS12 version of the certificate.
@ -162,7 +203,7 @@ def get_ssh_certificate_tokens(module, ssh_cert_path):
rc, stdout, stderr = module.run_command(['openssl', 'x509', '-in', ssh_cert_path, '-fingerprint', '-noout']) rc, stdout, stderr = module.run_command(['openssl', 'x509', '-in', ssh_cert_path, '-fingerprint', '-noout'])
if rc != 0: if rc != 0:
module.fail_json(msg="failed to generate the key fingerprint, error was: %s" % stderr) module.fail_json(msg="failed to generate the key fingerprint, error was: %s" % stderr)
fingerprint = stdout.strip()[17:].replace(':','') fingerprint = stdout.strip()[17:].replace(':', '')
rc, stdout, stderr = module.run_command(['openssl', 'pkcs12', '-export', '-in', ssh_cert_path, '-nokeys', '-password', 'pass:']) rc, stdout, stderr = module.run_command(['openssl', 'pkcs12', '-export', '-in', ssh_cert_path, '-nokeys', '-password', 'pass:'])
if rc != 0: if rc != 0:
@ -182,8 +223,6 @@ def create_virtual_machine(module, azure):
Returns: Returns:
True if a new virtual machine was created, false otherwise True if a new virtual machine was created, false otherwise
""" """
name = module.params.get('name') name = module.params.get('name')
hostname = module.params.get('hostname') or name + ".cloudapp.net" hostname = module.params.get('hostname') or name + ".cloudapp.net"
endpoints = module.params.get('endpoints').split(',') endpoints = module.params.get('endpoints').split(',')
@ -198,24 +237,15 @@ def create_virtual_machine(module, azure):
wait_timeout = int(module.params.get('wait_timeout')) wait_timeout = int(module.params.get('wait_timeout'))
# Check if a deployment with the same name already exists # Check if a deployment with the same name already exists
deployment = None cloud_service_name_available = azure.check_hosted_service_name_availability(name)
try: if not cloud_service_name_available.result:
deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name)
except WindowsAzureMissingResourceError as e:
pass # no such deployment
except WindowsAzureError as e:
module.fail_json(msg="failed to create the new deployment, error was: %s" % str(e))
if deployment:
changed = False changed = False
else: else:
changed = True changed = True
# Create cloud service if necessary # Create cloud service if necessary
try: try:
existing_service_names = [service.service_name for service in azure.list_hosted_services()] result = azure.create_hosted_service(service_name=name, label=name, location=location)
if not name in existing_service_names: _wait_for_completion(azure, result, wait_timeout, "create_hosted_service")
azure.create_hosted_service(service_name=name, label=name, location=location)
except WindowsAzureError as e: except WindowsAzureError as e:
module.fail_json(msg="failed to create the new service name, it already exists: %s" % str(e)) module.fail_json(msg="failed to create the new service name, it already exists: %s" % str(e))
@ -227,7 +257,9 @@ def create_virtual_machine(module, azure):
if ssh_cert_path: if ssh_cert_path:
fingerprint, pkcs12_base64 = get_ssh_certificate_tokens(module, ssh_cert_path) fingerprint, pkcs12_base64 = get_ssh_certificate_tokens(module, ssh_cert_path)
# Add certificate to cloud service # Add certificate to cloud service
azure.add_service_certificate(name, pkcs12_base64, 'pfx', '') result = azure.add_service_certificate(name, pkcs12_base64, 'pfx', '')
_wait_for_completion(azure, result, wait_timeout, "add_service_certificate")
# Create ssh config # Create ssh config
ssh_config = SSH() ssh_config = SSH()
ssh_config.public_keys = PublicKeys() ssh_config.public_keys = PublicKeys()
@ -255,36 +287,25 @@ def create_virtual_machine(module, azure):
# Spin up virtual machine # Spin up virtual machine
try: try:
azure.create_virtual_machine_deployment(service_name=name, result = azure.create_virtual_machine_deployment(service_name=name,
deployment_name=name, deployment_name=name,
deployment_slot='production', deployment_slot='production',
label=name, label=name,
role_name=name, role_name=name,
system_config=linux_config, system_config=linux_config,
network_config=network_config, network_config=network_config,
os_virtual_hard_disk=os_hd, os_virtual_hard_disk=os_hd,
role_size=role_size) role_size=role_size)
_wait_for_completion(azure, result, wait_timeout, "create_virtual_machine_deployment")
except WindowsAzureError as e: except WindowsAzureError as e:
module.fail_json(msg="failed to create the new virtual machine, error was: %s" % str(e)) module.fail_json(msg="failed to create the new virtual machine, error was: %s" % str(e))
# wait here until the deployment is up
deployment = None
wait_timeout = time.time() + wait_timeout
while wait_timeout > time.time() and not deployment:
try:
deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name)
except WindowsAzureMissingResourceError as e:
pass # deployment still not available
except WindowsAzureError as e:
# got a bad response from azure, wait a second and then try again
time.sleep(1)
continue
if deployment:
break
else:
time.sleep(5)
return changed try:
deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name)
return (changed, urlparse(deployment.url).hostname, deployment)
except WindowsAzureError as e:
module.fail_json(msg="failed to lookup the deployment information for %s, error was: %s" % (name, str(e)))
def terminate_virtual_machine(module, azure): def terminate_virtual_machine(module, azure):
@ -309,10 +330,11 @@ def terminate_virtual_machine(module, azure):
changed = False changed = False
deployment = None deployment = None
disk_names = []
try: try:
deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name) deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name)
except WindowsAzureMissingResourceError as e: except WindowsAzureMissingResourceError as e:
pass # no such deployment pass # no such deployment or service
except WindowsAzureError as e: except WindowsAzureError as e:
module.fail_json(msg="failed to find the deployment, error was: %s" % str(e)) module.fail_json(msg="failed to find the deployment, error was: %s" % str(e))
@ -320,25 +342,27 @@ def terminate_virtual_machine(module, azure):
if deployment: if deployment:
changed = True changed = True
try: try:
# TODO: Also find a way to delete old hard drives # gather disk info
azure.delete_deployment(service_name=name,deployment_name=name) results = []
wait_timeout = time.time() + wait_timeout for role in deployment.role_list:
while wait_timeout > time.time() and deployment: role_props = azure.get_role(name, deployment.name, role.role_name)
try: if role_props.os_virtual_hard_disk.disk_name not in disk_names:
deployment = azure.get_deployment_by_name(service_name=name, deployment_name=name) disk_names.append(role_props.os_virtual_hard_disk.disk_name)
except WindowsAzureMissingResourceError as e:
break # successfully deleted result = azure.delete_deployment(name, deployment.name)
except WindowsAzureError as e: _wait_for_completion(azure, result, wait_timeout, "delete_deployment")
# Azure api error, wait a second and retry
time.sleep(1) for disk_name in disk_names:
continue azure.delete_disk(disk_name, True)
time.sleep(5)
# Now that the vm is deleted, remove the cloud service # Now that the vm is deleted, remove the cloud service
azure.delete_hosted_service(service_name=name) result = azure.delete_hosted_service(service_name=name)
_wait_for_completion(azure, result, wait_timeout, "delete_hosted_service")
except WindowsAzureError as e: except WindowsAzureError as e:
module.fail_json(msg="failed to delete the service %s, error was: %s" % (name, str(e))) module.fail_json(msg="failed to delete the service %s, error was: %s" % (name, str(e)))
return changed return changed, urlparse(deployment.url).hostname, deployment
def get_azure_creds(module): def get_azure_creds(module):
# Check modul args for credentials, then check environment vars # Check modul args for credentials, then check environment vars
@ -351,34 +375,41 @@ def get_azure_creds(module):
return subscription_id, management_cert_path return subscription_id, management_cert_path
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec = dict( argument_spec=dict(
ssh_cert_path = dict(), ssh_cert_path=dict(),
name = dict(), name=dict(),
hostname = dict(), hostname=dict(),
location = dict(choices=AZURE_LOCATIONS), location=dict(choices=AZURE_LOCATIONS),
role_size = dict(choices=AZURE_ROLE_SIZES), role_size=dict(choices=AZURE_ROLE_SIZES),
subscription_id = dict(no_log=True), subscription_id=dict(no_log=True),
storage_account = dict(), storage_account=dict(),
management_cert_path = dict(), management_cert_path=dict(),
endpoints = dict(default='22'), endpoints=dict(default='22'),
user = dict(), user=dict(),
password = dict(), password=dict(),
image = dict(), image=dict(),
state = dict(default='present'), state=dict(default='present'),
wait = dict(type='bool', default=False), wait=dict(type='bool', default=False),
wait_timeout = dict(default=300) wait_timeout=dict(default=600),
wait_timeout_redirects=dict(default=300)
) )
) )
# create azure ServiceManagementService object # create azure ServiceManagementService object
subscription_id, management_cert_path = get_azure_creds(module) subscription_id, management_cert_path = get_azure_creds(module)
azure = ServiceManagementService(subscription_id, management_cert_path)
if module.params.get('state') == 'absent': wait_timeout_redirects = int(module.params.get('wait_timeout_redirects'))
changed = terminate_virtual_machine(module, azure) if LooseVersion(windows_azure.__version__) <= "0.8.0":
# wrapper for handling redirects which the sdk <= 0.8.0 is not following
azure = Wrapper(ServiceManagementService(subscription_id, management_cert_path), wait_timeout_redirects)
else:
azure = ServiceManagementService(subscription_id, management_cert_path), wait_timeout_redirects
cloud_service_raw = None
if module.params.get('state') == 'absent':
(changed, public_dns_name, deployment) = terminate_virtual_machine(module, azure)
elif module.params.get('state') == 'present': elif module.params.get('state') == 'present':
# Changed is always set to true when provisioning new instances # Changed is always set to true when provisioning new instances
@ -392,10 +423,41 @@ def main():
module.fail_json(msg='location parameter is required for new instance') module.fail_json(msg='location parameter is required for new instance')
if not module.params.get('storage_account'): if not module.params.get('storage_account'):
module.fail_json(msg='storage_account parameter is required for new instance') module.fail_json(msg='storage_account parameter is required for new instance')
changed = create_virtual_machine(module, azure) (changed, public_dns_name, deployment) = create_virtual_machine(module, azure)
module.exit_json(changed=changed, public_dns_name=public_dns_name, deployment=json.loads(json.dumps(deployment, default=lambda o: o.__dict__)))
class Wrapper(object):
def __init__(self, obj, wait_timeout):
self.other = obj
self.wait_timeout = wait_timeout
def __getattr__(self, name):
if hasattr(self.other, name):
func = getattr(self.other, name)
return lambda *args, **kwargs: self._wrap(func, args, kwargs)
raise AttributeError(name)
def _wrap(self, func, args, kwargs):
if type(func) == MethodType:
result = self._handle_temporary_redirects(lambda: func(*args, **kwargs))
else:
result = self._handle_temporary_redirects(lambda: func(self.other, *args, **kwargs))
return result
def _handle_temporary_redirects(self, f):
wait_timeout = time.time() + self.wait_timeout
while wait_timeout > time.time():
try:
return f()
except WindowsAzureError as e:
if not str(e).lower().find("temporary redirect") == -1:
time.sleep(5)
pass
else:
raise e
module.exit_json(changed=changed)
# import module snippets # import module snippets
from ansible.module_utils.basic import * from ansible.module_utils.basic import *

@ -80,8 +80,9 @@ class AzureInventory(object):
elif not self.is_cache_valid(): elif not self.is_cache_valid():
self.do_api_calls_update_cache() self.do_api_calls_update_cache()
# Data to print if self.args.list_images:
if self.args.list: data_to_print = self.json_format_dict(self.get_images(), True)
elif self.args.list:
# Display list of nodes for inventory # Display list of nodes for inventory
if len(self.inventory) == 0: if len(self.inventory) == 0:
data_to_print = self.get_inventory_from_cache() data_to_print = self.get_inventory_from_cache()
@ -90,6 +91,13 @@ class AzureInventory(object):
print data_to_print print data_to_print
def get_images(self):
images = []
for image in self.sms.list_os_images():
if str(image.label).lower().find(self.args.list_images.lower()) >= 0:
images.append(vars(image))
return json.loads(json.dumps(images, default=lambda o: o.__dict__))
def is_cache_valid(self): def is_cache_valid(self):
"""Determines if the cache file has expired, or if it is still valid.""" """Determines if the cache file has expired, or if it is still valid."""
if os.path.isfile(self.cache_path_cache): if os.path.isfile(self.cache_path_cache):
@ -131,6 +139,8 @@ class AzureInventory(object):
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Azure') parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Azure')
parser.add_argument('--list', action='store_true', default=True, parser.add_argument('--list', action='store_true', default=True,
help='List nodes (default: True)') help='List nodes (default: True)')
parser.add_argument('--list-images', action='store',
help='Get all available images.')
parser.add_argument('--refresh-cache', action='store_true', default=False, parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to Azure (default: False - use cache files)') help='Force refresh of cache by making API requests to Azure (default: False - use cache files)')
self.args = parser.parse_args() self.args = parser.parse_args()

Loading…
Cancel
Save