Imported from https://github.com/dddrew/salt-dimensiondata (credits @dddrew) worked on PEP8 compliance.

This commit is contained in:
anthony-shaw 2016-02-01 09:25:39 +11:00
parent e202ce1ce0
commit e6601a81da

View File

@ -0,0 +1,548 @@
'''
Dimension Data Cloud Module
===========================
This is a cloud module for the Dimension Data Cloud,
using the existing Libcloud driver for Dimension Data.
.. code-block:: yaml
# Note: This example is for /etc/salt/cloud.providers
# or any file in the
# /etc/salt/cloud.providers.d/ directory.
my-dimensiondata-config:
user_id: my_username
key: myPassword!
region: dd-na
driver: dimensiondata
'''
# Import python libs
from __future__ import absolute_import
import logging
import socket
import pprint
# Import libcloud
try:
from libcloud.compute.base import NodeState
from libcloud.compute.base import NodeAuthPassword
from libcloud.loadbalancer.base import Member
from libcloud.loadbalancer.types import Provider as Provider_lb
from libcloud.loadbalancer.providers import get_driver as get_driver_lb
HAS_LIBCLOUD = True
except ImportError:
HAS_LIBCLOUD = False
# Import generic libcloud functions
from salt.cloud.libcloudfuncs import *
# Import salt libs
import salt.utils
# Import salt.cloud libs
import salt.utils.cloud
import salt.utils.pycrypto as sup
import salt.config as config
from salt.utils import namespaced_function
from salt.exceptions import (
SaltCloudConfigError,
SaltCloudNotFound,
SaltCloudSystemExit,
SaltCloudExecutionFailure,
SaltCloudExecutionTimeout
)
# Import netaddr IP matching
try:
from netaddr import all_matching_cidrs
HAS_NETADDR = True
except ImportError:
HAS_NETADDR = False
# Get logging started
log = logging.getLogger(__name__)
__virtualname__ = 'dimensiondata'
# Some of the libcloud functions need to be in the same namespace as the
# functions defined in the module, so we create new function objects inside
# this module namespace
get_size = namespaced_function(get_size, globals())
get_image = namespaced_function(get_image, globals())
avail_locations = namespaced_function(avail_locations, globals())
avail_images = namespaced_function(avail_images, globals())
avail_sizes = namespaced_function(avail_sizes, globals())
script = namespaced_function(script, globals())
destroy = namespaced_function(destroy, globals())
reboot = namespaced_function(reboot, globals())
list_nodes = namespaced_function(list_nodes, globals())
list_nodes_full = namespaced_function(list_nodes_full, globals())
list_nodes_select = namespaced_function(list_nodes_select, globals())
show_instance = namespaced_function(show_instance, globals())
get_node = namespaced_function(get_node, globals())
# Only load in this module is the DIMENSIONDATA configurations are in place
def __virtual__():
'''
Set up the libcloud functions and check for GCE configurations.
'''
if get_configured_provider() is False:
return False
if get_dependencies() is False:
return False
for provider, details in six.iteritems(__opts__['providers']):
if 'dimensiondata' not in details:
continue
return __virtualname__
def get_configured_provider():
'''
Return the first configured instance.
'''
return config.is_provider_configured(
__opts__,
__active_provider_name__ or 'dimensiondata',
('user_id', 'key', 'region')
)
def get_dependencies():
'''
Warn if dependencies aren't met.
'''
deps = {
'libcloud': HAS_LIBCLOUD,
'netaddr': HAS_NETADDR
}
return config.check_driver_dependencies(
__virtualname__,
deps
)
def create(vm_):
'''
Create a single VM from a data dict
'''
try:
# Check for required profile parameters before sending any API calls.
if vm_['profile'] and config.is_profile_configured(
__opts__,
__active_provider_name__ or 'dimensiondata',
vm_['profile']) is False:
return False
except AttributeError:
pass
# Since using "provider: <provider-engine>" is deprecated, alias provider
# to use driver: "driver: <provider-engine>"
if 'provider' in vm_:
vm_['driver'] = vm_.pop('provider')
salt.utils.cloud.fire_event(
'event',
'starting create',
'salt/cloud/{0}/creating'.format(vm_['name']),
{
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['driver'],
},
transport=__opts__['transport']
)
log.info('Creating Cloud VM {0}'.format(vm_['name']))
conn = get_conn()
rootPw = NodeAuthPassword(vm_['auth'])
try:
location = conn.ex_get_location_by_id(vm_['location'])
images = conn.list_images(location=location)
image = [x for x in images if x.id == vm_['image']][0]
networks = conn.ex_list_network_domains(location=location)
network_domain = [y for y in networks if y.name ==
vm_['network_domain']][0]
# Use the first VLAN in the network domain
vlan = conn.ex_list_vlans(location=location,
network_domain=network_domain)[0]
kwargs = {
'name': vm_['name'],
'image': image,
'auth': rootPw,
'ex_description': vm_['description'],
'ex_network_domain': network_domain,
'ex_vlan': vlan,
'ex_is_started': vm_['is_started']
}
data = conn.create_node(**kwargs)
except Exception as exc:
log.error(
'Error creating %s on DIMENSIONDATA\n\n'
'The following exception was thrown by libcloud when trying to '
'run the initial deployment: \n%s' %
(vm_['name'], exc),
exc_info_on_loglevel=logging.DEBUG
)
return False
def __query_node_data(vm_, data):
running = False
try:
node = show_instance(vm_['name'], 'action')
running = (node['state'] == NodeState.RUNNING)
log.debug(
'Loaded node data for %s:\nname: %s\nstate: %s' % (
vm_['name'],
pprint.pformat(node['name']),
node['state']
)
)
except Exception as err:
log.error(
'Failed to get nodes list: %s' % (
err
),
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG
)
# Trigger a failure in the wait for IP function
return False
if not running:
# Still not running, trigger another iteration
return
private = node['private_ips']
public = node['public_ips']
if private and not public:
log.warn(
'Private IPs returned, but not public... Checking for '
'misidentified IPs'
)
for private_ip in private:
private_ip = preferred_ip(vm_, [private_ip])
if salt.utils.cloud.is_public_ip(private_ip):
log.warn('%s is a public IP' % (private_ip))
data.public_ips.append(private_ip)
else:
log.warn('% is a private IP' % (private_ip))
if private_ip not in data.private_ips:
data.private_ips.append(private_ip)
if ssh_interface(vm_) == 'private_ips' and data.private_ips:
return data
if private:
data.private_ips = private
if ssh_interface(vm_) == 'private_ips':
return data
if public:
data.public_ips = public
if ssh_interface(vm_) != 'private_ips':
return data
log.debug('DATA')
log.debug(data)
try:
data = salt.utils.cloud.wait_for_ip(
__query_node_data,
update_args=(vm_, data),
timeout=config.get_cloud_config_value(
'wait_for_ip_timeout', vm_, __opts__, default=25 * 60),
interval=config.get_cloud_config_value(
'wait_for_ip_interval', vm_, __opts__, default=30),
max_failures=config.get_cloud_config_value(
'wait_for_ip_max_failures', vm_, __opts__, default=60),
)
except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc:
try:
# It might be already up, let's destroy it!
destroy(vm_['name'])
except SaltCloudSystemExit:
pass
finally:
raise SaltCloudSystemExit(str(exc))
log.debug('VM is now running')
if ssh_interface(vm_) == 'private_ips':
ip_address = preferred_ip(vm_, data.private_ips)
else:
ip_address = preferred_ip(vm_, data.public_ips)
log.debug('Using IP address %s' % (ip_address))
if salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'private_ips':
salt_ip_address = preferred_ip(vm_, data.private_ips)
log.info('Salt interface set to: %s' % (salt_ip_address))
else:
salt_ip_address = preferred_ip(vm_, data.public_ips)
log.debug('Salt interface set to: %s' % (salt_ip_address))
if not ip_address:
raise SaltCloudSystemExit(
'No IP addresses could be found.'
)
vm_['salt_host'] = salt_ip_address
vm_['ssh_host'] = ip_address
vm_['password'] = vm_['auth']
ret = salt.utils.cloud.bootstrap(vm_, __opts__)
ret.update(data.__dict__)
if 'password' in data.extra:
del data.extra['password']
log.info('Created Cloud VM \'{0[name]}\''.format(vm_))
log.debug(
'\'{0[name]}\' VM creation details:\n{1}'.format(
vm_, pprint.pformat(data.__dict__)
)
)
salt.utils.cloud.fire_event(
'event',
'created instance',
'salt/cloud/{0}/created'.format(vm_['name']),
{
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['driver'],
},
transport=__opts__['transport']
)
return ret
def create_lb(kwargs=None, call=None):
'''
Create a load-balancer configuration.
CLI Example:
.. code-block:: bash
salt-cloud -f create_lb dimensiondata \
name=dev-lb port=80 protocol=http \
members=w1,w2,w3 algorithm=ROUND_ROBIN
'''
conn = get_conn()
if call != 'function':
raise SaltCloudSystemExit(
'The create_lb function must be called with -f or --function.'
)
if not kwargs or 'name' not in kwargs:
log.error(
'A name must be specified when creating a health check.'
)
return False
if 'port' not in kwargs:
log.error(
'A port or port-range must be specified for the load-balancer.'
)
return False
if 'networkdomain' not in kwargs:
log.error(
'A network domain must be specified for the load-balancer.'
)
return False
if 'members' in kwargs:
members = []
ip = ""
membersList = kwargs.get('members').split(',')
log.debug('MemberList: {0}'.format(membersList))
for member in membersList:
try:
log.debug('Member: {0}'.format(member))
node = get_node(conn,member)
log.debug('Node: {0}'.format(node))
ip = node.private_ips[0]
except Exception as err:
log.error(
'Failed to get node ip: {0}'.format(
err
),
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG
)
members.append(Member(ip, ip, kwargs['port']))
else:
members = None
log.debug('Members: {0}'.format(members))
networkdomain = kwargs['networkdomain']
name = kwargs['name']
port = kwargs['port']
protocol = kwargs.get('protocol', None)
algorithm = kwargs.get('algorithm', None)
lb_conn = get_lb_conn(conn)
network_domains = conn.ex_list_network_domains()
network_domain = [y for y in network_domains if y.name == networkdomain][0]
log.debug('Network Domain: {0}'.format(network_domain.id))
lb_conn.ex_set_current_network_domain(network_domain.id)
salt.utils.cloud.fire_event(
'event',
'create load_balancer',
'salt/cloud/loadbalancer/creating',
kwargs,
transport=__opts__['transport']
)
lb = lb_conn.create_balancer(
name, port, protocol, algorithm, members
)
salt.utils.cloud.fire_event(
'event',
'created load_balancer',
'salt/cloud/loadbalancer/created',
kwargs,
transport=__opts__['transport']
)
return _expand_balancer(lb)
def _expand_balancer(lb):
'''
Convert the libcloud load-balancer object into something more serializable.
'''
ret = {}
ret.update(lb.__dict__)
return ret
def preferred_ip(vm_, ips):
'''
Return the preferred Internet protocol. Either 'ipv4' (default) or 'ipv6'.
'''
proto = config.get_cloud_config_value(
'protocol', vm_, __opts__, default='ipv4', search_global=False
)
family = socket.AF_INET
if proto == 'ipv6':
family = socket.AF_INET6
for ip in ips:
try:
socket.inet_pton(family, ip)
return ip
except Exception:
continue
return False
def ssh_interface(vm_):
'''
Return the ssh_interface type to connect to. Either 'public_ips' (default)
or 'private_ips'.
'''
return config.get_cloud_config_value(
'ssh_interface', vm_, __opts__, default='public_ips',
search_global=False
)
def stop(name, call=None):
'''
Stop a VM in DimensionData.
name
The name of the VM to stop.
CLI Example:
.. code-block:: bash
salt-cloud -a stop vm_name
'''
conn = get_conn()
node = get_node(conn, name)
log.debug('Node of Cloud VM: {0}'.format(node))
status = conn.ex_shutdown_graceful(node)
log.debug('Status of Cloud VM: {0}'.format(status))
return status
def start(name, call=None):
'''
Stop a VM in DimensionData.
name
The name of the VM to stop.
CLI Example:
.. code-block:: bash
salt-cloud -a stop vm_name
'''
conn = get_conn()
node = get_node(conn, name)
log.debug('Node of Cloud VM: {0}'.format(node))
status = conn.ex_start_node(node)
log.debug('Status of Cloud VM: {0}'.format(status))
return status
def get_conn():
'''
Return a conn object for the passed VM data
'''
vm_ = get_configured_provider()
driver = get_driver(Provider.DIMENSIONDATA)
region = config.get_cloud_config_value(
'region', vm_, __opts__
)
user_id = config.get_cloud_config_value(
'user_id', vm_, __opts__
)
key = config.get_cloud_config_value(
'key', vm_, __opts__
)
if key is not None:
log.debug('DimensionData authenticating using password')
return driver(
user_id,
key,
region=region
)
def get_lb_conn(dd_driver=None):
'''
Return a load-balancer conn object
'''
vm_ = get_configured_provider()
driver = get_driver(Provider.DIMENSIONDATA)
region = config.get_cloud_config_value(
'region', vm_, __opts__
)
user_id = config.get_cloud_config_value(
'user_id', vm_, __opts__
)
key = config.get_cloud_config_value(
'key', vm_, __opts__
)
if not dd_driver:
raise SaltCloudSystemExit(
'Missing dimensiondata_driver for get_lb_conn method.'
)
return get_driver_lb(Provider_lb.DIMENSIONDATA)(user_id, key, region=region)