Merge remote-tracking branch 'upstream/2015.8' into develop

This commit is contained in:
Pedro Algarvio 2015-07-08 11:47:25 +01:00
commit 39c1e32f47
12 changed files with 746 additions and 332 deletions

View file

@ -60,6 +60,7 @@ def beacon(config):
except OSError:
# Ensure a valid mount point
log.error('{0} is not a valid mount point, skipping.'.format(mount))
continue
current_usage = _current_usage.percent
monitor_usage = diskusage[mount]

View file

@ -1176,11 +1176,15 @@ def _wait_for_job(linode_id, job_id, timeout=300, quiet=True):
time.sleep(interval)
if not quiet:
log.info('Still waiting on Job {0} for {1}'.format(job_id,
linode_id))
log.info('Still waiting on Job {0} for Linode {1}.'.format(
job_id,
linode_id)
)
else:
log.debug('Still waiting on Job {0} for {1}'.format(job_id,
linode_id))
log.debug('Still waiting on Job {0} for Linode {1}.'.format(
job_id,
linode_id)
)
return False
@ -1203,6 +1207,8 @@ def _wait_for_status(linode_id, status=None, timeout=300, quiet=True):
if status is None:
status = _get_status_id_by_name('brand_new')
status_desc_waiting = _get_status_descr_by_id(status)
interval = 5
iterations = int(timeout / interval)
@ -1212,11 +1218,21 @@ def _wait_for_status(linode_id, status=None, timeout=300, quiet=True):
if result['STATUS'] == status:
return True
status_desc_result = _get_status_descr_by_id(result['STATUS'])
time.sleep(interval)
if quiet:
log.info('Status for {0} is {1}, waiting for {2}'.format(linode_id, result['STATUS'], status))
log.info('Status for Linode {0} is \'{1}\', waiting for \'{2}\'.'.format(
linode_id,
status_desc_result,
status_desc_waiting)
)
else:
log.debug('Status for {0} is {1}, waiting for {2}'.format(linode_id, result, status))
log.debug('Status for Linode {0} is \'{1}\', waiting for \'{2}\'.'.format(
linode_id,
status_desc_result,
status_desc_waiting)
)
return False

View file

@ -6,30 +6,27 @@ Module for handling openstack glance calls.
:configuration: This module is not usable until the following are specified
either in a pillar or in the minion's config file::
keystone.user: admin
keystone.password: verybadpass
keystone.tenant: admin
keystone.tenant_id: f80919baedab48ec8931f200c65a50df
keystone.insecure: False #(optional)
keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
glance.user: admin
glance.password: verybadpass
glance.tenant: admin
glance.insecure: False #(optional)
glance.auth_url: 'http://127.0.0.1:5000/v2.0/'
If configuration for multiple openstack accounts is required, they can be
set up as different configuration profiles:
For example::
openstack1:
keystone.user: admin
keystone.password: verybadpass
keystone.tenant: admin
keystone.tenant_id: f80919baedab48ec8931f200c65a50df
keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
glance.user: admin
glance.password: verybadpass
glance.tenant: admin
glance.auth_url: 'http://127.0.0.1:5000/v2.0/'
openstack2:
keystone.user: admin
keystone.password: verybadpass
keystone.tenant: admin
keystone.tenant_id: f80919baedab48ec8931f200c65a50df
keystone.auth_url: 'http://127.0.0.2:5000/v2.0/'
glance.user: admin
glance.password: verybadpass
glance.tenant: admin
glance.auth_url: 'http://127.0.0.2:5000/v2.0/'
With this configuration in place, any of the keystone functions can make use
of a configuration profile by declaring it explicitly.
@ -42,16 +39,34 @@ Module for handling openstack glance calls.
from __future__ import absolute_import
# Import third party libs
import salt.ext.six as six
#import salt.ext.six as six
from salt.exceptions import (
#CommandExecutionError,
SaltInvocationError
)
# pylint: disable=import-error
HAS_GLANCE = False
try:
from glanceclient import client
import glanceclient.v1.images
from glanceclient import exc
HAS_GLANCE = True
except ImportError:
pass
# Workaround, as the Glance API v2 requires you to
# already have a keystone token
HAS_KEYSTONE = False
try:
from keystoneclient.v2_0 import client as kstone
HAS_KEYSTONE = True
except ImportError:
pass
import logging
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)
import pprint
def __virtual__():
'''
@ -65,21 +80,86 @@ def __virtual__():
__opts__ = {}
def _auth(profile=None):
def _auth(profile=None, api_version=2, **connection_args):
'''
Set up keystone credentials
Set up glance credentials, returns
`glanceclient.client.Client`. Optional parameter
"api_version" defaults to 2.
Only intended to be used within glance-enabled modules
'''
kstone = __salt__['keystone.auth'](profile)
token = kstone.auth_token
endpoint = kstone.service_catalog.url_for(
service_type='image',
endpoint_type='publicURL',
)
return client.Client('1', endpoint, token=token)
if profile:
prefix = profile + ":glance."
else:
prefix = "glance."
# look in connection_args first, then default to config file
def get(key, default=None):
'''
TODO: Add docstring.
'''
return connection_args.get('connection_' + key,
__salt__['config.get'](prefix + key, default))
user = get('user', 'admin')
password = get('password', 'ADMIN')
tenant = get('tenant', 'admin')
tenant_id = get('tenant_id')
auth_url = get('auth_url', 'http://127.0.0.1:35357/v2.0/')
insecure = get('insecure', False)
token = get('token')
region = get('region')
endpoint = get('endpoint', 'http://127.0.0.1:9292/')
if token:
kwargs = {'token': token,
'username': user,
'endpoint_url': endpoint,
'auth_url': auth_url,
'region_name': region,
'tenant_name': tenant}
else:
kwargs = {'username': user,
'password': password,
'tenant_id': tenant_id,
'auth_url': auth_url,
'region_name': region,
'tenant_name': tenant}
# 'insecure' keyword not supported by all v2.0 keystone clients
# this ensures it's only passed in when defined
if insecure:
kwargs['insecure'] = True
if token:
log.debug('Calling glanceclient.client.Client(' +
'{0}, {1}, **{2})'.format(api_version, endpoint, kwargs))
try:
return client.Client(api_version, endpoint, **kwargs)
except exc.HTTPUnauthorized:
kwargs.pop('token')
kwargs['password'] = password
log.warn('Supplied token is invalid, trying to ' +
'get a new one using username and password.')
if HAS_KEYSTONE:
# TODO: redact kwargs['password']
log.debug('Calling keystoneclient.v2_0.client.Client(' +
'{0}, **{1})'.format(endpoint, kwargs))
keystone = kstone.Client(**kwargs)
log.debug(help(keystone.get_token))
kwargs['token'] = keystone.get_token(keystone.session)
kwargs.pop('password')
log.debug('Calling glanceclient.client.Client(' +
'{0}, {1}, **{2})'.format(api_version, endpoint, kwargs))
return client.Client(api_version, endpoint, **kwargs)
else:
raise NotImplementedError(
"Can't retrieve a auth_token without keystone")
def image_create(profile=None, **kwargs):
def image_create(name, location, profile=None, visibility='public',
container_format='bare', disk_format='raw'):
'''
Create an image (glance image-create)
@ -87,20 +167,35 @@ def image_create(profile=None, **kwargs):
.. code-block:: bash
salt '*' glance.image_create name=f16-jeos is_public=true \\
salt '*' glance.image_create name=f16-jeos visibility=public \\
disk_format=qcow2 container_format=ovf \\
copy_from=http://berrange.fedorapeople.org/images/2012-02-29/f16-x86_64-openstack-sda.qcow2
copy_from=http://berrange.fedorapeople.org/\
images/2012-02-29/f16-x86_64-openstack-sda.qcow2
For all possible values, run ``glance help image-create`` on the minion.
'''
nt_ks = _auth(profile)
fields = dict(
[(k, v) for (k, v) in six.iteritems(kwargs) if k in glanceclient.v1.images.CREATE_PARAMS]
)
image = nt_ks.images.create(**fields)
newimage = image_list(str(image.id), profile)
return {newimage['name']: newimage}
# valid options for "visibility":
v_list = ['public', 'private']
# valid options for "container_format":
cf_list = ['ami', 'ari', 'aki', 'bare', 'ovf']
# valid options for "disk_format":
df_list = ['ami', 'ari', 'aki', 'vhd', 'vmdk',
'raw', 'qcow2', 'vdi', 'iso']
if visibility not in v_list:
raise SaltInvocationError('"visibility" needs to be one ' +
'of the following: {0}'.format(', '.join(v_list)))
if container_format not in cf_list:
raise SaltInvocationError('"container_format" needs to be ' +
'one of the following: {0}'.format(', '.join(cf_list)))
if disk_format not in df_list:
raise SaltInvocationError('"disk_format" needs to be one ' +
'of the following: {0}'.format(', '.join(df_list)))
# Icehouse's glanceclient doesn't have add_location() and
# glanceclient.v2 doesn't implement Client.images.create()
# in a usable fashion. Thus we have to use v1 for now.
g_client = _auth(profile, api_version=1)
image = g_client.images.create(name=name, copy_from=location)
return image_show(image.id)
def image_delete(id=None, name=None, profile=None): # pylint: disable=C0103
@ -115,15 +210,15 @@ def image_delete(id=None, name=None, profile=None): # pylint: disable=C0103
salt '*' glance.image_delete id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df
salt '*' glance.image_delete name=f16-jeos
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
if name:
for image in nt_ks.images.list():
for image in g_client.images.list():
if image.name == name:
id = image.id # pylint: disable=C0103
continue
if not id:
return {'Error': 'Unable to resolve image id'}
nt_ks.images.delete(id)
g_client.images.delete(id)
ret = 'Deleted image with ID {0}'.format(id)
if name:
ret += ' ({0})'.format(name)
@ -138,35 +233,29 @@ def image_show(id=None, name=None, profile=None): # pylint: disable=C0103
.. code-block:: bash
salt '*' glance.image_get
salt '*' glance.image_show
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
ret = {}
if name:
for image in nt_ks.images.list():
for image in g_client.images.list():
if image.name == name:
id = image.id # pylint: disable=C0103
continue
if not id:
return {'Error': 'Unable to resolve image id'}
image = nt_ks.images.get(id)
ret[image.name] = {
'id': image.id,
'name': image.name,
'checksum': image.checksum,
'container_format': image.container_format,
'created_at': image.created_at,
'deleted': image.deleted,
'disk_format': image.disk_format,
'is_public': image.is_public,
'min_disk': image.min_disk,
'min_ram': image.min_ram,
'owner': image.owner,
'protected': image.protected,
'size': image.size,
'status': image.status,
'updated_at': image.updated_at,
}
image = g_client.images.get(id)
pformat = pprint.PrettyPrinter(indent=4).pformat
log.debug('Properties of image {0}:\n{1}'.format(
image.name, pformat(image)))
# TODO: Get rid of the wrapping dict, see #24568
ret[image.name] = {}
schema = image_schema(profile=profile)
if len(schema.keys()) == 1:
schema = schema['image']
for key in schema.keys():
if key in image:
ret[image.name][key] = image[key]
return ret
@ -180,31 +269,59 @@ def image_list(id=None, profile=None): # pylint: disable=C0103
salt '*' glance.image_list
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
ret = {}
for image in nt_ks.images.list():
# TODO: Get rid of the wrapping dict, see #24568
for image in g_client.images.list():
ret[image.name] = {
'id': image.id,
'name': image.name,
'checksum': image.checksum,
'container_format': image.container_format,
'created_at': image.created_at,
'deleted': image.deleted,
'disk_format': image.disk_format,
'is_public': image.is_public,
'file': image.file,
'min_disk': image.min_disk,
'min_ram': image.min_ram,
'owner': image.owner,
'protected': image.protected,
'size': image.size,
'status': image.status,
'tags': image.tags,
'updated_at': image.updated_at,
'visibility': image.visibility,
}
# Those cause AttributeErrors in Icehouse' glanceclient
for attr in ['container_format', 'disk_format', 'size']:
if attr in image:
ret[image.name][attr] = image[attr]
if id == image.id:
return ret[image.name]
return ret
def image_schema(profile=None):
'''
Returns names and descriptions of the schema "image"'s
properties for this profile's instance of glance
'''
return schema_get('image', profile)
def schema_get(name, profile=None):
'''
Known valid names of schemas are:
- image
- images
- member
- members
'''
g_client = _auth(profile)
pformat = pprint.PrettyPrinter(indent=4).pformat
schema_props = {}
for prop in g_client.schemas.get(name).properties:
schema_props[prop.name] = prop.description
log.debug('Properties of schema {0}:\n{1}'.format(
name, pformat(schema_props)))
return {name: schema_props}
def _item_list(profile=None):
'''
Template for writing list functions
@ -216,17 +333,18 @@ def _item_list(profile=None):
salt '*' glance.item_list
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
ret = []
for item in nt_ks.items.list():
for item in g_client.items.list():
ret.append(item.__dict__)
#ret[item.name] = {
# 'name': item.name,
# }
return ret
# The following is a list of functions that need to be incorporated in the
# glance module. This list should be updated as functions are added.
#The following is a list of functions that need to be incorporated in the
#glance module. This list should be updated as functions are added.
# image-download Download a specific image.
# image-update Update a specific image.

View file

@ -121,6 +121,26 @@ def list_(show_all=False, where=None, return_yaml=True):
return {'schedule': {}}
def is_enabled(name):
'''
List a Job only if its enabled
.. versionadded:: 2015.5.3
CLI Example:
.. code-block:: bash
salt '*' schedule.is_enabled name=job_name
'''
current_schedule = __salt__['schedule.list'](show_all=False, return_yaml=False)
if name in current_schedule:
return current_schedule[name]
else:
return {}
def purge(**kwargs):
'''
Purge all the jobs currently scheduled on the minion

View file

@ -350,6 +350,8 @@ def available(name):
salt '*' service.available sshd
'''
name = _canonical_template_unit_name(name)
if name.endswith('.service'):
name = name[:-8] # len('.service') is 8
units = get_all()
if name in units:
return True

View file

@ -50,6 +50,12 @@ def start():
if 'num_processes' not in mod_opts:
mod_opts['num_processes'] = 1
if mod_opts['num_processes'] > 1 and mod_opts.get('debug', False) is True:
raise Exception((
'Tornado\'s debug implementation is not compatible with multiprocess. '
'Either disable debug, or set num_processes to 1.'
))
paths = [
(r"/", saltnado.SaltAPIHandler),
(r"/login", saltnado.SaltAuthHandler),
@ -85,7 +91,6 @@ def start():
application.opts = __opts__
application.mod_opts = mod_opts
application.auth = salt.auth.LoadAuth(__opts__)
application.event_listener = saltnado.EventListener(mod_opts, __opts__)
# the kwargs for the HTTPServer
kwargs = {}
@ -111,7 +116,7 @@ def start():
)
http_server.start(mod_opts['num_processes'])
except:
print('Rest_tornado unable to bind to port {0}'.format(mod_opts['port']))
logger.error('Rest_tornado unable to bind to port {0}'.format(mod_opts['port']), exc_info=True)
raise SystemExit(1)
try:

View file

@ -258,7 +258,8 @@ class EventListener(object):
'master',
opts['sock_dir'],
opts['transport'],
opts=opts)
opts=opts,
)
self.event.subscribe() # start listening for events immediately
@ -271,8 +272,10 @@ class EventListener(object):
# map of future -> timeout_callback
self.timeout_map = {}
self.stream = zmqstream.ZMQStream(self.event.sub,
io_loop=tornado.ioloop.IOLoop.current())
self.stream = zmqstream.ZMQStream(
self.event.sub,
io_loop=tornado.ioloop.IOLoop.current(),
)
self.stream.on_recv(self._handle_event_socket_recv)
def clean_timeout_futures(self, request):
@ -391,6 +394,17 @@ class BaseSaltAPIHandler(tornado.web.RequestHandler, SaltClientsMixIn): # pylin
self.write("400 Invalid Client: Client not found in salt clients")
self.finish()
def initialize(self):
'''
Initialize the handler before requests are called
'''
if not hasattr(self.application, 'event_listener'):
logger.critical('init a listener')
self.application.event_listener = EventListener(
self.application.mod_opts,
self.application.opts,
)
@property
def token(self):
'''

View file

@ -312,7 +312,7 @@
from __future__ import absolute_import, print_function
import sys
import inspect
#import textwrap
import textwrap
import functools
# Import salt libs
@ -531,7 +531,10 @@ class Configuration(six.with_metaclass(ConfigurationMeta, object)):
if cls.title is not None:
serialized['title'] = cls.title
if cls.description is not None:
serialized['description'] = cls.description
if cls.description == cls.__doc__:
serialized['description'] = textwrap.dedent(cls.description).strip()
else:
serialized['description'] = cls.description
required = []
ordering = []
@ -741,6 +744,12 @@ class BaseConfigItem(BaseItem):
serialized[argname] = argvalue
return serialized
def __get_description__(self):
if self.description is not None:
if self.description == self.__doc__:
return textwrap.dedent(self.description).strip()
return self.description
#def render_as_rst(self, name):
# '''
# Render the configuration item as a restructured text string
@ -1026,21 +1035,24 @@ class ArrayConfig(BaseConfigItem):
super(ArrayConfig, self).__init__(**kwargs)
def __validate_attributes__(self):
if self.items is not None:
if isinstance(self.items, (list, tuple)):
for item in self.items:
if not isinstance(item, (Configuration, BaseItem)):
raise RuntimeError(
'All items passed in the item argument tuple/list must be '
'a subclass of Configuration, BaseItem or BaseConfigItem, '
'not {0}'.format(type(item))
)
elif not isinstance(self.items, (Configuration, BaseItem)):
raise RuntimeError(
'The items argument passed must be a subclass of '
'Configuration, BaseItem or BaseConfigItem, not '
'{0}'.format(type(self.items))
)
if not self.items:
raise RuntimeError(
'The passed items must not be empty'
)
if isinstance(self.items, (list, tuple)):
for item in self.items:
if not isinstance(item, (Configuration, BaseItem)):
raise RuntimeError(
'All items passed in the item argument tuple/list must be '
'a subclass of Configuration, BaseItem or BaseConfigItem, '
'not {0}'.format(type(item))
)
elif not isinstance(self.items, (Configuration, BaseItem)):
raise RuntimeError(
'The items argument passed must be a subclass of '
'Configuration, BaseItem or BaseConfigItem, not '
'{0}'.format(type(self.items))
)
def __get_items__(self):
if isinstance(self.items, (Configuration, BaseItem)):
@ -1054,6 +1066,141 @@ class ArrayConfig(BaseConfigItem):
return items
class DictConfig(BaseConfigItem):
__type__ = 'object'
__serialize_attr_aliases__ = {
'min_properties': 'minProperties',
'max_properties': 'maxProperties',
'pattern_properties': 'patternProperties',
'additional_properties': 'additionalProperties'
}
properties = None
pattern_properties = None
additional_properties = None
min_properties = None
max_properties = None
def __init__(self,
properties=None,
pattern_properties=None,
additional_properties=None,
min_properties=None,
max_properties=None,
**kwargs):
'''
:param required:
If the configuration item is required. Defaults to ``False``.
:type required:
boolean
:param title:
A short explanation about the purpose of the data described by this item.
:type title:
str
:param description:
A detailed explanation about the purpose of the data described by this item.
:param default:
The default value for this configuration item. May be :data:`.Null` (a special value
to set the default value to null).
:param enum:
A list(list, tuple, set) of valid choices.
:param properties:
A dictionary containing fields
:param pattern_properties:
A dictionary whose keys are regular expressions (ECMA 262).
Properties match against these regular expressions, and for any that match,
the property is described by the corresponding field schema.
:type pattern_properties: dict[str -> :class:`.Configuration` or
:class:`.BaseItem` or :class:`.BaseItemConfig`]
:param additional_properties:
Describes properties that are not described by the ``properties`` or ``pattern_properties``.
:type additional_properties: bool or :class:`.Configuration` or :class:`.BaseItem`
or :class:`.BaseItemConfig`
:param min_properties:
A minimum number of properties.
:type min_properties: int
:param max_properties:
A maximum number of properties
:type max_properties: int
'''
if properties is not None:
self.properties = properties
if pattern_properties is not None:
self.pattern_properties = pattern_properties
if additional_properties is not None:
self.additional_properties = additional_properties
if min_properties is not None:
self.min_properties = min_properties
if max_properties is not None:
self.max_properties = max_properties
super(DictConfig, self).__init__(**kwargs)
def __validate_attributes__(self):
if not self.properties and not self.pattern_properties:
raise RuntimeError(
'One of properties or pattern properties must be passed'
)
if self.properties is not None:
if not isinstance(self.properties, dict):
raise RuntimeError(
'The passed properties must be passed as a dict not '
'\'{0}\''.format(type(self.properties))
)
for key, prop in self.properties.items():
if not isinstance(prop, (Configuration, BaseItem)):
raise RuntimeError(
'The passed property who\'s key is \'{0}\' must be of type '
'Configuration, BaseItem or BaseConfigItem, not '
'\'{1}\''.format(key, type(prop))
)
if self.pattern_properties is not None:
if not isinstance(self.pattern_properties, dict):
raise RuntimeError(
'The passed pattern_properties must be passed as a dict '
'not \'{0}\''.format(type(self.pattern_properties))
)
for key, prop in self.pattern_properties.items():
if not isinstance(prop, (Configuration, BaseItem)):
raise RuntimeError(
'The passed pattern_property who\'s key is \'{0}\' must '
'be of type Configuration, BaseItem or BaseConfigItem, '
'not \'{1}\''.format(key, type(prop))
)
if self.additional_properties is not None:
if not isinstance(self.additional_properties, (bool, Configuration, BaseItem)):
raise RuntimeError(
'The passed additional_properties must be of type bool, '
'Configuration, BaseItem or BaseConfigItem, not \'{0}\''.format(
type(self.pattern_properties)
)
)
def __get_properties__(self):
if self.properties is None:
return
properties = OrderedDict()
for key, prop in self.properties.items():
properties[key] = prop.serialize()
return properties
def __get_pattern_properties__(self):
if self.pattern_properties is None:
return
pattern_properties = OrderedDict()
for key, prop in self.pattern_properties.items():
pattern_properties[key] = prop.serialize()
return pattern_properties
def __get_additional_properties__(self):
if self.additional_properties is None:
return
if isinstance(self.additional_properties, bool):
return self.additional_properties
return self.additional_properties.serialize()
class OneOfConfig(BaseItem):
__type__ = 'oneOf'

View file

@ -464,6 +464,7 @@ def _interfaces_ip(out):
based on the current set of cols
'''
brd = None
scope = None
if '/' in value: # we have a CIDR in this address
ip, cidr = value.split('/') # pylint: disable=C0103
else:
@ -476,7 +477,8 @@ def _interfaces_ip(out):
brd = cols[cols.index('brd') + 1]
elif type_ == 'inet6':
mask = cidr
return (ip, mask, brd)
scope = cols[cols.index('scope') + 1]
return (ip, mask, brd, scope)
groups = re.compile('\r?\n\\d').split(out)
for group in groups:
@ -503,7 +505,7 @@ def _interfaces_ip(out):
iflabel = cols[-1:][0]
if type_ in ('inet', 'inet6'):
if 'secondary' not in cols:
ipaddr, netmask, broadcast = parse_network(value, cols)
ipaddr, netmask, broadcast, scope = parse_network(value, cols)
if type_ == 'inet':
if 'inet' not in data:
data['inet'] = list()
@ -519,11 +521,12 @@ def _interfaces_ip(out):
addr_obj = dict()
addr_obj['address'] = ipaddr
addr_obj['prefixlen'] = netmask
addr_obj['scope'] = scope
data['inet6'].append(addr_obj)
else:
if 'secondary' not in data:
data['secondary'] = list()
ip_, mask, brd = parse_network(value, cols)
ip_, mask, brd, scp = parse_network(value, cols)
data['secondary'].append({
'type': type_,
'address': ip_,
@ -531,7 +534,7 @@ def _interfaces_ip(out):
'broadcast': brd,
'label': iflabel,
})
del ip_, mask, brd
del ip_, mask, brd, scp
elif type_.startswith('link'):
data['hwaddr'] = value
if iface:

View file

@ -411,6 +411,8 @@ class Build(build):
class Install(install):
user_options = install.user_options + [
('salt-transport=', None, 'The transport to prepare salt for. Choices are \'zeromq\' '
'\'raet\' or \'both\'. Defaults to \'zeromq\'', 'zeromq'),
('salt-root-dir=', None,
'Salt\'s pre-configured root directory'),
('salt-config-dir=', None,
@ -453,6 +455,7 @@ class Install(install):
self.salt_base_master_roots_dir = None
self.salt_logs_dir = None
self.salt_pidfile_dir = None
self.salt_transport = None
def finalize_options(self):
install.finalize_options(self)

View file

@ -1,203 +0,0 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Rupesh Tare <rupesht@saltstack.com>`
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing Libs
from salttesting import TestCase, skipIf
from salttesting.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
# Import Salt Libs
from salt.modules import glance
@skipIf(NO_MOCK, NO_MOCK_REASON)
class GlanceTestCase(TestCase):
'''
Test cases for salt.modules.glance
'''
def test_image_create(self):
'''
Test for Create an image (glance image-create)
'''
with patch.object(glance, '_auth', retur_value=True):
with patch.object(glance, 'image_list', return_value={'name': 'A'}):
self.assertEqual(glance.image_create(), {'A': {'name': 'A'}})
def test_image_delete(self):
'''
Test for Delete an image (glance image-delete)
'''
class MockObjects(object):
def __init__(self):
self.name = 'A'
self.id = 1
self.checksum = 'B'
self.container_format = 'C'
self.created_at = 'D'
self.deleted = 'E'
self.disk_format = 'F'
self.is_public = 'G'
self.min_disk = 'H'
self.min_ram = 'I'
self.owner = 'J'
self.protected = 'K'
self.size = 'L'
self.status = 'M'
self.updated_at = 'N'
class MockImages(object):
def list(self):
return [MockObjects()]
def delete(self, key):
return
class MockClient(object):
def __init__(self):
self.images = MockImages()
mock_lst = MagicMock(return_value=MockClient())
with patch.object(glance, '_auth', mock_lst):
self.assertEqual(glance.image_delete(id=1, name='A', profile='B'),
'Deleted image with ID 1 (A)')
with patch.object(glance, '_auth', return_value=True):
self.assertEqual(glance.image_delete(),
{'Error': 'Unable to resolve image id'})
def test_image_show(self):
'''
Test to return details about a specific image (glance image-show)
'''
with patch.object(glance, '_auth', return_value=True):
self.assertEqual(glance.image_show(),
{'Error': 'Unable to resolve image id'})
class MockObjects(object):
def __init__(self):
self.name = 'A'
self.id = 1
self.checksum = 'B'
self.container_format = 'C'
self.created_at = 'D'
self.deleted = 'E'
self.disk_format = 'F'
self.is_public = 'G'
self.min_disk = 'H'
self.min_ram = 'I'
self.owner = 'J'
self.protected = 'K'
self.size = 'L'
self.status = 'M'
self.updated_at = 'N'
class MockImages(object):
def list(self):
return [MockObjects()]
def get(self, key):
return MockObjects()
class MockClient(object):
def __init__(self):
self.images = MockImages()
mock_lst = MagicMock(return_value=MockClient())
with patch.object(glance, '_auth', mock_lst):
self.assertDictEqual(glance.image_show(name='A', profile='B'),
{'A': {'status': 'M',
'deleted': 'E',
'container_format': 'C',
'min_ram': 'I',
'updated_at': 'N',
'owner': 'J',
'min_disk': 'H',
'is_public': 'G',
'id': 1,
'size': 'L',
'name': 'A',
'checksum': 'B',
'created_at': 'D',
'disk_format': 'F',
'protected': 'K'}})
def test_image_list(self):
'''
Test to return a list of available images (glance image-list)
'''
class MockObjects(object):
def __init__(self):
self.name = 'A'
self.id = 1
self.checksum = 'B'
self.container_format = 'C'
self.created_at = 'D'
self.deleted = 'E'
self.disk_format = 'F'
self.is_public = 'G'
self.min_disk = 'H'
self.min_ram = 'I'
self.owner = 'J'
self.protected = 'K'
self.size = 'L'
self.status = 'M'
self.updated_at = 'N'
class MockImages(object):
def list(self):
return [MockObjects()]
def get(self, key):
return MockObjects()
class MockClient(object):
def __init__(self):
self.images = MockImages()
mock_lst = MagicMock(return_value=MockClient())
with patch.object(glance, '_auth', mock_lst):
self.assertDictEqual(glance.image_list(id=1, profile='B'),
{'checksum': 'B',
'container_format': 'C',
'created_at': 'D',
'deleted': 'E',
'disk_format': 'F',
'id': 1,
'is_public': 'G',
'min_disk': 'H',
'min_ram': 'I',
'name': 'A',
'owner': 'J',
'protected': 'K',
'size': 'L',
'status': 'M',
'updated_at': 'N'})
self.assertDictEqual(glance.image_list(),
{'A':
{'checksum': 'B',
'container_format': 'C',
'created_at': 'D',
'deleted': 'E',
'disk_format': 'F',
'id': 1,
'is_public': 'G',
'min_disk': 'H',
'min_ram': 'I',
'name': 'A',
'owner': 'J',
'protected': 'K',
'size': 'L',
'status': 'M',
'updated_at': 'N'}})
if __name__ == '__main__':
from integration import run_tests
run_tests(GlanceTestCase, needs_daemon=False)

View file

@ -50,6 +50,36 @@ class ConfigTestCase(TestCase):
TestCase for salt.utils.config module
'''
def test_configuration_subclass_inherits_items(self):
class BaseConfig(config.Configuration):
base = config.BooleanConfig(default=True, required=True)
class SubClassedConfig(BaseConfig):
hungry = config.BooleanConfig(title='Hungry', description='Are you hungry?', required=True)
self.assertDictEqual(
SubClassedConfig.serialize(),
{
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'properties': {
'base': {
'default': True,
'type': 'boolean',
'title': 'base'
},
'hungry': {
'type': 'boolean',
'description': 'Are you hungry?',
'title': 'Hungry'
}
},
'required': ['base', 'hungry'],
'x-ordering': ['base', 'hungry'],
'additionalProperties': False,
}
)
def test_boolean_config(self):
item = config.BooleanConfig(title='Hungry', description='Are you hungry?')
self.assertDictEqual(
@ -767,15 +797,6 @@ class ConfigTestCase(TestCase):
self.assertIn('is not one of', excinfo.exception.message)
def test_array_config(self):
item = config.ArrayConfig(title='Dog Names', description='Name your dogs')
self.assertDictEqual(
item.serialize(), {
'type': 'array',
'title': item.title,
'description': item.description
}
)
string_item = config.StringConfig(title='Dog Name',
description='The dog name')
item = config.ArrayConfig(title='Dog Names',
@ -885,20 +906,6 @@ class ConfigTestCase(TestCase):
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_array_config_validation(self):
class TestConf(config.Configuration):
item = config.ArrayConfig(title='Dog Names', description='Name your dogs')
try:
jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': 1}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is not of type', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
@ -918,6 +925,7 @@ class ConfigTestCase(TestCase):
class TestConf(config.Configuration):
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
items=config.StringConfig(),
min_items=1,
max_items=2)
@ -999,6 +1007,286 @@ class ConfigTestCase(TestCase):
format_checker=jsonschema.FormatChecker())
self.assertIn('is not one of', excinfo.exception.message)
def test_dict_config(self):
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'sides': config.IntegerConfig()
}
)
self.assertDictEqual(
item.serialize(), {
'type': 'object',
'title': item.title,
'description': item.description,
'properties': {
'sides': {'type': 'integer'}
}
}
)
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'sides': config.IntegerConfig()
},
min_properties=1,
max_properties=2
)
self.assertDictEqual(
item.serialize(), {
'type': 'object',
'title': item.title,
'description': item.description,
'properties': {
'sides': {'type': 'integer'}
},
'minProperties': 1,
'maxProperties': 2
}
)
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
pattern_properties={
's*': config.IntegerConfig()
},
min_properties=1,
max_properties=2
)
self.assertDictEqual(
item.serialize(), {
'type': 'object',
'title': item.title,
'description': item.description,
'patternProperties': {
's*': {'type': 'integer'}
},
'minProperties': 1,
'maxProperties': 2
}
)
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'color': config.StringConfig(enum=['red', 'green', 'blue'])
},
pattern_properties={
's*': config.IntegerConfig()
},
min_properties=1,
max_properties=2
)
self.assertDictEqual(
item.serialize(), {
'type': 'object',
'title': item.title,
'description': item.description,
'properties': {
'color': {
'type': 'string',
'enum': ['red', 'green', 'blue']
}
},
'patternProperties': {
's*': {'type': 'integer'}
},
'minProperties': 1,
'maxProperties': 2
}
)
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'color': config.StringConfig(enum=['red', 'green', 'blue'])
},
pattern_properties={
's*': config.IntegerConfig()
},
additional_properties=True,
min_properties=1,
max_properties=2
)
self.assertDictEqual(
item.serialize(), {
'type': 'object',
'title': item.title,
'description': item.description,
'properties': {
'color': {
'type': 'string',
'enum': ['red', 'green', 'blue']
}
},
'patternProperties': {
's*': {'type': 'integer'}
},
'minProperties': 1,
'maxProperties': 2,
'additionalProperties': True
}
)
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'sides': config.IntegerConfig()
},
additional_properties=config.OneOfConfig(items=[config.BooleanConfig(),
config.StringConfig()])
)
self.assertDictEqual(
item.serialize(), {
'type': 'object',
'title': item.title,
'description': item.description,
'properties': {
'sides': {'type': 'integer'}
},
'additionalProperties': {
'oneOf': [
{'type': 'boolean'},
{'type': 'string'}
]
}
}
)
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_dict_config_validation(self):
class TestConf(config.Configuration):
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'sides': config.IntegerConfig()
}
)
try:
jsonschema.validate({'item': {'sides': 1}}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': {'sides': '1'}}, TestConf.serialize())
self.assertIn('is not of type', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': 2}, TestConf.serialize())
self.assertIn('is not of type', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'color': config.StringConfig(enum=['red', 'green', 'blue'])
},
pattern_properties={
'si.*': config.IntegerConfig()
},
)
try:
jsonschema.validate({'item': {'sides': 1, 'color': 'red'}}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': {'sides': '4', 'color': 'blue'}}, TestConf.serialize())
self.assertIn('is not of type', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': 2}, TestConf.serialize())
self.assertIn('is not of type', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'color': config.StringConfig(enum=['red', 'green', 'blue'])
},
pattern_properties={
'si.*': config.IntegerConfig()
},
additional_properties=False
)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': {'color': 'green', 'sides': 4, 'surfaces': 4}}, TestConf.serialize())
self.assertIn('Additional properties are not allowed', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'color': config.StringConfig(enum=['red', 'green', 'blue'])
},
additional_properties=config.OneOfConfig(items=[
config.BooleanConfig(),
config.IntegerConfig()
])
)
try:
jsonschema.validate({'item': {'sides': 1,
'color': 'red',
'rugged_surface': False}}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': {'sides': '4', 'color': 'blue'}}, TestConf.serialize())
self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.DictConfig(
title='Poligon',
description='Describe the Poligon',
properties={
'color': config.StringConfig(enum=['red', 'green', 'blue'])
},
additional_properties=config.OneOfConfig(items=[
config.BooleanConfig(),
config.IntegerConfig()
]),
min_properties=2,
max_properties=3
)
try:
jsonschema.validate({'item': {'color': 'red', 'sides': 1}}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate({'item': {'sides': 1, 'color': 'red', 'rugged_surface': False}}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': {'color': 'blue'}}, TestConf.serialize())
self.assertIn('does not have enough properties', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': {'sides': 4,
'color': 'blue',
'rugged_surface': False,
'opaque': True}}, TestConf.serialize())
self.assertIn('has too many properties', excinfo.exception.message)
def test_oneof_config(self):
item = config.OneOfConfig(
items=(config.StringConfig(title='Yes', enum=['yes']),