mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Added Packet cloud driver (#37542)
* Added packet cloud driver * Pylint fixes * Fixed pylint errors * More pylint fixes
This commit is contained in:
parent
01b3883650
commit
15c40813f5
605
salt/cloud/clouds/packet.py
Normal file
605
salt/cloud/clouds/packet.py
Normal file
@ -0,0 +1,605 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Packet Cloud Module Using Packet's Python API Client
|
||||
===========================================
|
||||
|
||||
The Packet cloud module is used to control access to the Packet VPS system.
|
||||
|
||||
Use of this module only requires the ``token`` parameter.
|
||||
|
||||
Set up the cloud configuration at ``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/packet.conf``:
|
||||
|
||||
The Packet profile requires ``size``, ``image``, ``location``, ``project_id``
|
||||
|
||||
Optional profile parameters:
|
||||
|
||||
- ``storage_size`` - min value is 10, defines Gigabytes of storage that will be attached to device.
|
||||
- ``storage_tier`` - storage_1 - Standard Plan, storage_2 - Performance Plan
|
||||
- ``snapshot_count`` - int
|
||||
- ``snapshot_frequency`` - string - possible values:
|
||||
- 1min
|
||||
- 15min
|
||||
- 1hour
|
||||
- 1day
|
||||
- 1week
|
||||
- 1month
|
||||
- 1year
|
||||
|
||||
This driver requires Packet's client library: https://pypi.python.org/pypi/packet-python
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packet-provider:
|
||||
minion:
|
||||
master: 192.168.50.10
|
||||
driver: packet
|
||||
token: ewr23rdf35wC8oNjJrhmHa87rjSXzJyi
|
||||
private_key: /root/.ssh/id_rsa
|
||||
|
||||
packet-profile:
|
||||
provider: packet-provider
|
||||
size: baremetal_0
|
||||
image: ubuntu_16_04_image
|
||||
location: ewr1
|
||||
project_id: a64d000b-d47c-4d26-9870-46aac43010a6
|
||||
storage_size: 10
|
||||
storage_tier: storage_1
|
||||
storage_snapshot_count: 1
|
||||
storage_snapshot_frequency: 15min
|
||||
'''
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
import pprint
|
||||
import time
|
||||
import packet
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.config as config
|
||||
|
||||
from salt.ext.six.moves import range
|
||||
|
||||
from salt.exceptions import (
|
||||
SaltCloudException,
|
||||
SaltCloudSystemExit
|
||||
)
|
||||
|
||||
# Import Salt-Cloud Libs
|
||||
import salt.utils.cloud
|
||||
|
||||
from salt.cloud.libcloudfuncs import get_size, get_image, script, show_instance
|
||||
from salt.utils import namespaced_function
|
||||
|
||||
get_size = namespaced_function(get_size, globals())
|
||||
get_image = namespaced_function(get_image, globals())
|
||||
|
||||
script = namespaced_function(script, globals())
|
||||
|
||||
show_instance = namespaced_function(show_instance, globals())
|
||||
|
||||
# Get logging started
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
__virtualname__ = 'packet'
|
||||
|
||||
|
||||
# Only load this module if the Packet configuration is in place.
|
||||
def __virtual__():
|
||||
'''
|
||||
Check for Packet configs.
|
||||
'''
|
||||
if get_configured_provider() is False:
|
||||
return False
|
||||
|
||||
return __virtualname__
|
||||
|
||||
|
||||
def get_configured_provider():
|
||||
'''
|
||||
Return the first configured instance.
|
||||
'''
|
||||
return config.is_provider_configured(
|
||||
__opts__,
|
||||
__active_provider_name__ or __virtualname__,
|
||||
('token',)
|
||||
)
|
||||
|
||||
|
||||
def avail_images(call=None):
|
||||
'''
|
||||
Return available Packet os images.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-cloud --list-images packet-provider
|
||||
salt-cloud -f avail_images packet-provider
|
||||
'''
|
||||
if call == 'action':
|
||||
raise SaltCloudException(
|
||||
'The avail_images function must be called with -f or --function.'
|
||||
)
|
||||
|
||||
ret = {}
|
||||
|
||||
vm_ = get_configured_provider()
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
ret = {}
|
||||
|
||||
for os_system in manager.list_operating_systems():
|
||||
ret[os_system.name] = os_system.__dict__
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def avail_locations(call=None):
|
||||
'''
|
||||
Return available Packet datacenter locations.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-cloud --list-locations packet-provider
|
||||
salt-cloud -f avail_locations packet-provider
|
||||
'''
|
||||
if call == 'action':
|
||||
raise SaltCloudException(
|
||||
'The avail_locations function must be called with -f or --function.'
|
||||
)
|
||||
|
||||
vm_ = get_configured_provider()
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
ret = {}
|
||||
|
||||
for facility in manager.list_facilities():
|
||||
ret[facility.name] = facility.__dict__
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def avail_sizes(call=None):
|
||||
'''
|
||||
Return available Packet sizes.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-cloud --list-sizes packet-provider
|
||||
salt-cloud -f avail_sizes packet-provider
|
||||
'''
|
||||
if call == 'action':
|
||||
raise SaltCloudException(
|
||||
'The avail_locations function must be called with -f or --function.'
|
||||
)
|
||||
|
||||
vm_ = get_configured_provider()
|
||||
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
ret = {}
|
||||
|
||||
for plan in manager.list_plans():
|
||||
ret[plan.name] = plan.__dict__
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def avail_projects(call=None):
|
||||
'''
|
||||
Return available Packet projects.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-cloud -f avail_projects packet-provider
|
||||
'''
|
||||
if call == 'action':
|
||||
raise SaltCloudException(
|
||||
'The avail_projects function must be called with -f or --function.'
|
||||
)
|
||||
|
||||
vm_ = get_configured_provider()
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
ret = {}
|
||||
|
||||
for project in manager.list_projects():
|
||||
ret[project.name] = project.__dict__
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _wait_for_status(status_type, object_id, status=None, timeout=500, quiet=True):
|
||||
'''
|
||||
Wait for a certain status from Packet.
|
||||
status_type
|
||||
device or volume
|
||||
object_id
|
||||
The ID of the Packet device or volume to wait on. Required.
|
||||
status
|
||||
The status to wait for.
|
||||
timeout
|
||||
The amount of time to wait for a status to update.
|
||||
quiet
|
||||
Log status updates to debug logs when False. Otherwise, logs to info.
|
||||
'''
|
||||
if status is None:
|
||||
status = "ok"
|
||||
|
||||
interval = 5
|
||||
iterations = int(timeout / interval)
|
||||
|
||||
vm_ = get_configured_provider()
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
for i in range(0, iterations):
|
||||
get_object = getattr(manager, "get_{status_type}".format(status_type=status_type))
|
||||
obj = get_object(object_id)
|
||||
|
||||
if obj.state == status:
|
||||
return obj
|
||||
|
||||
time.sleep(interval)
|
||||
if quiet:
|
||||
log.info('Status for Packet {0} is \'{1}\', waiting for \'{2}\'.'.format(
|
||||
object_id,
|
||||
obj.state,
|
||||
status)
|
||||
)
|
||||
else:
|
||||
log.debug('Status for Packet {0} is \'{1}\', waiting for \'{2}\'.'.format(
|
||||
object_id,
|
||||
obj.state,
|
||||
status)
|
||||
)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def is_profile_configured(vm_):
|
||||
try:
|
||||
# Check for required profile parameters before sending any API calls.
|
||||
if vm_['profile'] and config.is_profile_configured(__opts__,
|
||||
__active_provider_name__ or __virtualname__,
|
||||
vm_['profile'],
|
||||
vm_=vm_) is False:
|
||||
return False
|
||||
|
||||
alias, driver = __active_provider_name__.split(':')
|
||||
|
||||
profile_data = __opts__['providers'][alias][driver]['profiles'][vm_['profile']]
|
||||
|
||||
if profile_data.get('storage_size') or profile_data.get('storage_tier'):
|
||||
required_keys = ['storage_size', 'storage_tier']
|
||||
|
||||
for key in required_keys:
|
||||
if profile_data.get(key) is None:
|
||||
log.error(
|
||||
'both storage_size and storage_tier required for profile {profile}. '
|
||||
'Please check your profile configuration'.format(profile=vm_['profile'])
|
||||
)
|
||||
return False
|
||||
|
||||
locations = avail_locations()
|
||||
|
||||
for location in locations.values():
|
||||
if location['code'] == profile_data['location']:
|
||||
if 'storage' not in location['features']:
|
||||
log.error(
|
||||
'Choosen location {location} for profile {profile} does not support storage feature. '
|
||||
'Please check your profile configuration'.format(
|
||||
location=location['code'], profile=vm_['profile']
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
if profile_data.get('storage_snapshot_count') or profile_data.get('storage_snapshot_frequency'):
|
||||
required_keys = ['storage_size', 'storage_tier']
|
||||
|
||||
for key in required_keys:
|
||||
if profile_data.get(key) is None:
|
||||
log.error(
|
||||
'both storage_snapshot_count and storage_snapshot_frequency required for profile {profile}. '
|
||||
'Please check your profile configuration'.format(profile=vm_['profile'])
|
||||
)
|
||||
return False
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def create(vm_):
|
||||
'''
|
||||
Create a single Packet VM.
|
||||
'''
|
||||
name = vm_['name']
|
||||
|
||||
if not is_profile_configured(vm_):
|
||||
return False
|
||||
|
||||
# Since using "provider: <provider-engine>" is deprecated, alias provider
|
||||
# to use driver: "driver: <provider-engine>"
|
||||
if 'provider' in vm_:
|
||||
vm_['driver'] = vm_.pop('provider')
|
||||
|
||||
__utils__['cloud.fire_event'](
|
||||
'event',
|
||||
'starting create',
|
||||
'salt/cloud/{0}/creating'.format(name),
|
||||
args={
|
||||
'name': name,
|
||||
'profile': vm_['profile'],
|
||||
'provider': vm_['driver'],
|
||||
},
|
||||
sock_dir=__opts__['sock_dir'],
|
||||
transport=__opts__['transport']
|
||||
)
|
||||
|
||||
log.info('Creating Packet VM {0}'.format(name))
|
||||
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
device = manager.create_device(project_id=vm_['project_id'],
|
||||
hostname=name,
|
||||
plan=vm_['size'], facility=vm_['location'],
|
||||
operating_system=vm_['image'])
|
||||
|
||||
device = _wait_for_status('device', device.id, status="active")
|
||||
|
||||
if device.state != "active":
|
||||
log.error(
|
||||
'Error creating {0} on PACKET\n\n'
|
||||
'while waiting for initial ready status'.format(name),
|
||||
exc_info_on_loglevel=logging.DEBUG
|
||||
)
|
||||
|
||||
# Define which ssh_interface to use
|
||||
ssh_interface = _get_ssh_interface(vm_)
|
||||
|
||||
# Pass the correct IP address to the bootstrap ssh_host key
|
||||
if ssh_interface == 'private_ips':
|
||||
for ip in device.ip_addresses:
|
||||
if ip['public'] is False:
|
||||
vm_['ssh_host'] = ip['address']
|
||||
break
|
||||
else:
|
||||
for ip in device.ip_addresses:
|
||||
if ip['public'] is True:
|
||||
vm_['ssh_host'] = ip['address']
|
||||
break
|
||||
|
||||
key_filename = config.get_cloud_config_value(
|
||||
'private_key', vm_, __opts__, search_global=False, default=None
|
||||
)
|
||||
|
||||
vm_['key_filename'] = key_filename
|
||||
|
||||
vm_['private_key'] = key_filename
|
||||
|
||||
# Bootstrap!
|
||||
ret = __utils__['cloud.bootstrap'](vm_, __opts__)
|
||||
|
||||
ret.update({'device': device.__dict__})
|
||||
|
||||
if vm_.get('storage_tier') and vm_.get('storage_size'):
|
||||
# create storage and attach it to device
|
||||
|
||||
volume = manager.create_volume(
|
||||
vm_['project_id'], "{0}_storage".format(name), vm_.get('storage_tier'),
|
||||
vm_.get('storage_size'), vm_.get('location'), snapshot_count=vm_.get('storage_snapshot_count', 0),
|
||||
snapshot_frequency=vm_.get('storage_snapshot_frequency'))
|
||||
|
||||
volume.attach(device.id)
|
||||
|
||||
volume = _wait_for_status('volume', volume.id, status="active")
|
||||
|
||||
if volume.state != "active":
|
||||
log.error(
|
||||
'Error creating {0} on PACKET\n\n'
|
||||
'while waiting for initial ready status'.format(name),
|
||||
exc_info_on_loglevel=logging.DEBUG
|
||||
)
|
||||
|
||||
ret.update({'volume': volume.__dict__})
|
||||
|
||||
log.info('Created Cloud VM \'{0}\''.format(name))
|
||||
|
||||
log.debug(
|
||||
'\'{0}\' VM creation details:\n{1}'.format(
|
||||
name, pprint.pformat(device.__dict__)
|
||||
)
|
||||
)
|
||||
|
||||
__utils__['cloud.fire_event'](
|
||||
'event',
|
||||
'created instance',
|
||||
'salt/cloud/{0}/created'.format(name),
|
||||
args={
|
||||
'name': name,
|
||||
'profile': vm_['profile'],
|
||||
'provider': vm_['driver'],
|
||||
},
|
||||
sock_dir=__opts__['sock_dir'],
|
||||
transport=__opts__['transport']
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def list_nodes_full(call=None):
|
||||
'''
|
||||
List devices, with all available information.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-cloud -F
|
||||
salt-cloud --full-query
|
||||
salt-cloud -f list_nodes_full packet-provider
|
||||
|
||||
..
|
||||
'''
|
||||
if call == 'action':
|
||||
raise SaltCloudException(
|
||||
'The list_nodes_full function must be called with -f or --function.'
|
||||
)
|
||||
|
||||
ret = {}
|
||||
|
||||
for device in get_devices_by_token():
|
||||
ret[device.hostname] = device.__dict__
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def list_nodes_min(call=None):
|
||||
'''
|
||||
Return a list of the VMs that are on the provider. Only a list of VM names and
|
||||
their state is returned. This is the minimum amount of information needed to
|
||||
check for existing VMs.
|
||||
|
||||
.. versionadded:: 2015.8.0
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-cloud -f list_nodes_min packet-provider
|
||||
salt-cloud --function list_nodes_min packet-provider
|
||||
'''
|
||||
if call == 'action':
|
||||
raise SaltCloudSystemExit(
|
||||
'The list_nodes_min function must be called with -f or --function.'
|
||||
)
|
||||
|
||||
ret = {}
|
||||
|
||||
for device in get_devices_by_token():
|
||||
ret[device.hostname] = {'id': device.id, 'state': device.state}
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def list_nodes_select(call=None):
|
||||
'''
|
||||
Return a list of the VMs that are on the provider, with select fields.
|
||||
'''
|
||||
return salt.utils.cloud.list_nodes_select(
|
||||
list_nodes_full(), __opts__['query.selection'], call,
|
||||
)
|
||||
|
||||
|
||||
def get_devices_by_token():
|
||||
vm_ = get_configured_provider()
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
devices = []
|
||||
|
||||
for profile_name in vm_['profiles'].keys():
|
||||
profile = vm_['profiles'][profile_name]
|
||||
|
||||
devices.extend(manager.list_devices(profile['project_id']))
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def list_nodes(call=None):
|
||||
'''
|
||||
Returns a list of devices, keeping only a brief listing.
|
||||
CLI Example:
|
||||
.. code-block:: bash
|
||||
salt-cloud -Q
|
||||
salt-cloud --query
|
||||
salt-cloud -f list_nodes packet-provider
|
||||
..
|
||||
'''
|
||||
|
||||
if call == 'action':
|
||||
raise SaltCloudException(
|
||||
'The list_nodes function must be called with -f or --function.'
|
||||
)
|
||||
|
||||
ret = {}
|
||||
|
||||
for device in get_devices_by_token():
|
||||
ret[device.hostname] = device.__dict__
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def destroy(name, call=None):
|
||||
'''
|
||||
Destroys a Packet device by name.
|
||||
|
||||
name
|
||||
The hostname of VM to be be destroyed.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-cloud -d name
|
||||
'''
|
||||
if call == 'function':
|
||||
raise SaltCloudException(
|
||||
'The destroy action must be called with -d, --destroy, '
|
||||
'-a or --action.'
|
||||
)
|
||||
|
||||
__utils__['cloud.fire_event'](
|
||||
'event',
|
||||
'destroying instance',
|
||||
'salt/cloud/{0}/destroying'.format(name),
|
||||
args={'name': name},
|
||||
sock_dir=__opts__['sock_dir'],
|
||||
transport=__opts__['transport']
|
||||
)
|
||||
|
||||
vm_ = get_configured_provider()
|
||||
manager = packet.Manager(auth_token=vm_['token'])
|
||||
|
||||
nodes = list_nodes_min()
|
||||
|
||||
node = nodes[name]
|
||||
|
||||
for project in manager.list_projects():
|
||||
|
||||
for volume in manager.list_volumes(project.id):
|
||||
if volume.attached_to == node['id']:
|
||||
volume.detach()
|
||||
volume.delete()
|
||||
break
|
||||
|
||||
manager.call_api("devices/{id}".format(id=node['id']), type='DELETE')
|
||||
|
||||
__utils__['cloud.fire_event'](
|
||||
'event',
|
||||
'destroyed instance',
|
||||
'salt/cloud/{0}/destroyed'.format(name),
|
||||
args={'name': name},
|
||||
sock_dir=__opts__['sock_dir'],
|
||||
transport=__opts__['transport']
|
||||
)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def _get_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
|
||||
)
|
Loading…
Reference in New Issue
Block a user