salt/modules/glassfish.py
# -*- coding: utf-8 -*-
'''
Module for working with the Glassfish/Payara 4.x management API
.. versionadded:: Carbon
:depends: requests
'''
from __future__ import absolute_import, print_function, unicode_literals
try: # python2
from urllib import quote, unquote
except ImportError: # python3
from urllib.parse import quote, unquote
try:
import requests
import salt.defaults.exitcodes
import salt.utils.json
from salt.exceptions import CommandExecutionError
HAS_LIBS = True
except ImportError:
HAS_LIBS = False
__virtualname__ = 'glassfish'
# Default server
DEFAULT_SERVER = {'ssl': False, 'url': 'localhost', 'port': 4848, 'user': None, 'password': None}
def __virtual__():
'''
Only load if requests is installed
'''
if HAS_LIBS:
return __virtualname__
else:
return False, 'The "{0}" module could not be loaded: ' \
'"requests" is not installed.'.format(__virtualname__)
def _get_headers():
'''
Return fixed dict with headers (JSON data + mandatory "Requested by" header)
'''
return {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-By': 'GlassFish REST HTML interface'
}
def _get_auth(username, password):
'''
Returns the HTTP auth header
'''
if username and password:
return requests.auth.HTTPBasicAuth(username, password)
else:
return None
def _get_url(ssl, url, port, path):
'''
Returns the URL of the endpoint
'''
if ssl:
return 'https://{0}:{1}/management/domain/{2}'.format(url, port, path)
else:
return 'http://{0}:{1}/management/domain/{2}'.format(url, port, path)
def _get_server(server):
'''
Returns the server information if provided, or the defaults
'''
return server if server else DEFAULT_SERVER
def _clean_data(data):
'''
Removes SaltStack params from **kwargs
'''
for key in list(data):
if key.startswith('__pub'):
del data[key]
return data
def _api_response(response):
'''
Check response status code + success_code returned by glassfish
'''
if response.status_code == 404:
__context__['retcode'] = salt.defaults.exitcodes.SALT_BUILD_FAIL
raise CommandExecutionError('Element doesn\'t exists')
if response.status_code == 401:
__context__['retcode'] = salt.defaults.exitcodes.SALT_BUILD_FAIL
raise CommandExecutionError('Bad username or password')
elif response.status_code == 200 or response.status_code == 500:
try:
data = salt.utils.json.loads(response.content)
if data['exit_code'] != 'SUCCESS':
__context__['retcode'] = salt.defaults.exitcodes.SALT_BUILD_FAIL
raise CommandExecutionError(data['message'])
return data
except ValueError:
__context__['retcode'] = salt.defaults.exitcodes.SALT_BUILD_FAIL
raise CommandExecutionError('The server returned no data')
else:
response.raise_for_status()
def _api_get(path, server=None):
'''
Do a GET request to the API
'''
server = _get_server(server)
response = requests.get(
url=_get_url(server['ssl'], server['url'], server['port'], path),
auth=_get_auth(server['user'], server['password']),
headers=_get_headers(),
verify=False
)
return _api_response(response)
def _api_post(path, data, server=None):
'''
Do a POST request to the API
'''
server = _get_server(server)
response = requests.post(
url=_get_url(server['ssl'], server['url'], server['port'], path),
auth=_get_auth(server['user'], server['password']),
headers=_get_headers(),
data=salt.utils.json.dumps(data),
verify=False
)
return _api_response(response)
def _api_delete(path, data, server=None):
'''
Do a DELETE request to the API
'''
server = _get_server(server)
response = requests.delete(
url=_get_url(server['ssl'], server['url'], server['port'], path),
auth=_get_auth(server['user'], server['password']),
headers=_get_headers(),
params=data,
verify=False
)
return _api_response(response)
# "Middle layer": uses _api_* functions to enum/get/create/update/delete elements
def _enum_elements(name, server=None):
'''
Enum elements
'''
elements = []
data = _api_get(name, server)
if any(data['extraProperties']['childResources']):
for element in data['extraProperties']['childResources']:
elements.append(element)
return elements
return None
def _get_element_properties(name, element_type, server=None):
'''
Get an element's properties
'''
properties = {}
data = _api_get('{0}/{1}/property'.format(element_type, name), server)
# Get properties into a dict
if any(data['extraProperties']['properties']):
for element in data['extraProperties']['properties']:
properties[element['name']] = element['value']
return properties
return {}
def _get_element(name, element_type, server=None, with_properties=True):
'''
Get an element with or without properties
'''
element = {}
name = quote(name, safe='')
data = _api_get('{0}/{1}'.format(element_type, name), server)
# Format data, get properties if asked, and return the whole thing
if any(data['extraProperties']['entity']):
for key, value in data['extraProperties']['entity'].items():
element[key] = value
if with_properties:
element['properties'] = _get_element_properties(name, element_type)
return element
return None
def _create_element(name, element_type, data, server=None):
'''
Create a new element
'''
# Define property and id from name and properties + remove SaltStack parameters
if 'properties' in data:
data['property'] = ''
for key, value in data['properties'].items():
if not data['property']:
data['property'] += '{0}={1}'.format(key, value.replace(':', '\\:'))
else:
data['property'] += ':{0}={1}'.format(key, value.replace(':', '\\:'))
del data['properties']
# Send request
_api_post(element_type, _clean_data(data), server)
return unquote(name)
def _update_element(name, element_type, data, server=None):
'''
Update an element, including it's properties
'''
# Urlencode the name (names may have slashes)
name = quote(name, safe='')
# Update properties first
if 'properties' in data:
properties = []
for key, value in data['properties'].items():
properties.append({'name': key, 'value': value})
_api_post('{0}/{1}/property'.format(element_type, name), properties, server)
del data['properties']
# If the element only contained properties
if not data:
return unquote(name)
# Get the current data then merge updated data into it
update_data = _get_element(name, element_type, server, with_properties=False)
if update_data:
update_data.update(data)
else:
__context__['retcode'] = salt.defaults.exitcodes.SALT_BUILD_FAIL
raise CommandExecutionError('Cannot update {0}'.format(name))
# Finally, update the element
_api_post('{0}/{1}'.format(element_type, name), _clean_data(update_data), server)
return unquote(name)
def _delete_element(name, element_type, data, server=None):
'''
Delete an element
'''
_api_delete('{0}/{1}'.format(element_type, quote(name, safe='')), data, server)
return name
# Connector connection pools
def enum_connector_c_pool(server=None):
'''
Enum connection pools
'''
return _enum_elements('resources/connector-connection-pool', server)
def get_connector_c_pool(name, server=None):
'''
Get a specific connection pool
'''
return _get_element(name, 'resources/connector-connection-pool', server)
def create_connector_c_pool(name, server=None, **kwargs):
'''
Create a connection pool
'''
defaults = {
'connectionDefinitionName': 'javax.jms.ConnectionFactory',
'resourceAdapterName': 'jmsra',
'associateWithThread': False,
'connectionCreationRetryAttempts': 0,
'connectionCreationRetryIntervalInSeconds': 0,
'connectionLeakReclaim': False,
'connectionLeakTimeoutInSeconds': 0,
'description': '',
'failAllConnections': False,
'id': name,
'idleTimeoutInSeconds': 300,
'isConnectionValidationRequired': False,
'lazyConnectionAssociation': False,
'lazyConnectionEnlistment': False,
'matchConnections': True,
'maxConnectionUsageCount': 0,
'maxPoolSize': 32,
'maxWaitTimeInMillis': 60000,
'ping': False,
'poolResizeQuantity': 2,
'pooling': True,
'steadyPoolSize': 8,
'target': 'server',
'transactionSupport': '',
'validateAtmostOncePeriodInSeconds': 0
}
# Data = defaults + merge kwargs + remove salt
data = defaults
data.update(kwargs)
# Check TransactionSupport against acceptable values
if data['transactionSupport'] and data['transactionSupport'] not in (
'XATransaction',
'LocalTransaction',
'NoTransaction'
):
raise CommandExecutionError('Invalid transaction support')
return _create_element(name, 'resources/connector-connection-pool', data, server)
def update_connector_c_pool(name, server=None, **kwargs):
'''
Update a connection pool
'''
if 'transactionSupport' in kwargs and kwargs['transactionSupport'] not in (
'XATransaction',
'LocalTransaction',
'NoTransaction'
):
raise CommandExecutionError('Invalid transaction support')
return _update_element(name, 'resources/connector-connection-pool', kwargs, server)
def delete_connector_c_pool(name, target='server', cascade=True, server=None):
'''
Delete a connection pool
'''
data = {'target': target, 'cascade': cascade}
return _delete_element(name, 'resources/connector-connection-pool', data, server)
# Connector resources
def enum_connector_resource(server=None):
'''
Enum connection resources
'''
return _enum_elements('resources/connector-resource', server)
def get_connector_resource(name, server=None):
'''
Get a specific connection resource
'''
return _get_element(name, 'resources/connector-resource', server)
def create_connector_resource(name, server=None, **kwargs):
'''
Create a connection resource
'''
defaults = {
'description': '',
'enabled': True,
'id': name,
'poolName': '',
'objectType': 'user',
'target': 'server'
}
# Data = defaults + merge kwargs + poolname
data = defaults
data.update(kwargs)
if not data['poolName']:
raise CommandExecutionError('No pool name!')
# Fix for lowercase vs camelCase naming differences
for key, value in list(data.items()):
del data[key]
data[key.lower()] = value
return _create_element(name, 'resources/connector-resource', data, server)
def update_connector_resource(name, server=None, **kwargs):
'''
Update a connection resource
'''
# You're not supposed to update jndiName, if you do so, it will crash, silently
if 'jndiName' in kwargs:
del kwargs['jndiName']
return _update_element(name, 'resources/connector-resource', kwargs, server)
def delete_connector_resource(name, target='server', server=None):
'''
Delete a connection resource
'''
return _delete_element(name, 'resources/connector-resource', {'target': target}, server)
# JMS Destinations
def enum_admin_object_resource(server=None):
'''
Enum JMS destinations
'''
return _enum_elements('resources/admin-object-resource', server)
def get_admin_object_resource(name, server=None):
'''
Get a specific JMS destination
'''
return _get_element(name, 'resources/admin-object-resource', server)
def create_admin_object_resource(name, server=None, **kwargs):
'''
Create a JMS destination
'''
defaults = {
'description': '',
'className': 'com.sun.messaging.Queue',
'enabled': True,
'id': name,
'resAdapter': 'jmsra',
'resType': 'javax.jms.Queue',
'target': 'server'
}
# Data = defaults + merge kwargs + poolname
data = defaults
data.update(kwargs)
# ClassName isn't optional, even if the API says so
if data['resType'] == 'javax.jms.Queue':
data['className'] = 'com.sun.messaging.Queue'
elif data['resType'] == 'javax.jms.Topic':
data['className'] = 'com.sun.messaging.Topic'
else:
raise CommandExecutionError('resType should be "javax.jms.Queue" or "javax.jms.Topic"!')
if data['resAdapter'] != 'jmsra':
raise CommandExecutionError('resAdapter should be "jmsra"!')
# Fix for lowercase vs camelCase naming differences
if 'resType' in data:
data['restype'] = data['resType']
del data['resType']
if 'className' in data:
data['classname'] = data['className']
del data['className']
return _create_element(name, 'resources/admin-object-resource', data, server)
def update_admin_object_resource(name, server=None, **kwargs):
'''
Update a JMS destination
'''
if 'jndiName' in kwargs:
del kwargs['jndiName']
return _update_element(name, 'resources/admin-object-resource', kwargs, server)
def delete_admin_object_resource(name, target='server', server=None):
'''
Delete a JMS destination
'''
return _delete_element(name, 'resources/admin-object-resource', {'target': target}, server)
# JDBC Pools
def enum_jdbc_connection_pool(server=None):
'''
Enum JDBC pools
'''
return _enum_elements('resources/jdbc-connection-pool', server)
def get_jdbc_connection_pool(name, server=None):
'''
Get a specific JDBC pool
'''
return _get_element(name, 'resources/jdbc-connection-pool', server)
def create_jdbc_connection_pool(name, server=None, **kwargs):
'''
Create a connection resource
'''
defaults = {
'allowNonComponentCallers': False,
'associateWithThread': False,
'connectionCreationRetryAttempts': '0',
'connectionCreationRetryIntervalInSeconds': '10',
'connectionLeakReclaim': False,
'connectionLeakTimeoutInSeconds': '0',
'connectionValidationMethod': 'table',
'datasourceClassname': '',
'description': '',
'driverClassname': '',
'failAllConnections': False,
'idleTimeoutInSeconds': '300',
'initSql': '',
'isConnectionValidationRequired': False,
'isIsolationLevelGuaranteed': True,
'lazyConnectionAssociation': False,
'lazyConnectionEnlistment': False,
'matchConnections': False,
'maxConnectionUsageCount': '0',
'maxPoolSize': '32',
'maxWaitTimeInMillis': 60000,
'name': name,
'nonTransactionalConnections': False,
'ping': False,
'poolResizeQuantity': '2',
'pooling': True,
'resType': '',
'sqlTraceListeners': '',
'statementCacheSize': '0',
'statementLeakReclaim': False,
'statementLeakTimeoutInSeconds': '0',
'statementTimeoutInSeconds': '-1',
'steadyPoolSize': '8',
'target': 'server',
'transactionIsolationLevel': '',
'validateAtmostOncePeriodInSeconds': '0',
'validationClassname': '',
'validationTableName': '',
'wrapJdbcObjects': True
}
# Data = defaults + merge kwargs + poolname
data = defaults
data.update(kwargs)
# Check resType against acceptable values
if data['resType'] not in (
'javax.sql.DataSource',
'javax.sql.XADataSource',
'javax.sql.ConnectionPoolDataSource',
'java.sql.Driver'
):
raise CommandExecutionError('Invalid resource type')
# Check connectionValidationMethod against acceptable velues
if data['connectionValidationMethod'] not in (
'auto-commit',
'meta-data',
'table',
'custom-validation'
):
raise CommandExecutionError('Invalid connection validation method')
if data['transactionIsolationLevel'] \
and data['transactionIsolationLevel'] not in (
'read-uncommitted',
'read-committed',
'repeatable-read',
'serializable'
):
raise CommandExecutionError('Invalid transaction isolation level')
if not data['datasourceClassname'] \
and data['resType'] in (
'javax.sql.DataSource',
'javax.sql.ConnectionPoolDataSource',
'javax.sql.XADataSource'
):
raise CommandExecutionError('No datasource class name while using datasource resType')
if not data['driverClassname'] and data['resType'] == 'java.sql.Driver':
raise CommandExecutionError('No driver class nime while using driver resType')
return _create_element(name, 'resources/jdbc-connection-pool', data, server)
def update_jdbc_connection_pool(name, server=None, **kwargs):
'''
Update a JDBC pool
'''
return _update_element(name, 'resources/jdbc-connection-pool', kwargs, server)
def delete_jdbc_connection_pool(name, target='server', cascade=False, server=None):
'''
Delete a JDBC pool
'''
data = {'target': target, 'cascade': cascade}
return _delete_element(name, 'resources/jdbc-connection-pool', data, server)
# JDBC resources
def enum_jdbc_resource(server=None):
'''
Enum JDBC resources
'''
return _enum_elements('resources/jdbc-resource', server)
def get_jdbc_resource(name, server=None):
'''
Get a specific JDBC resource
'''
return _get_element(name, 'resources/jdbc-resource', server)
def create_jdbc_resource(name, server=None, **kwargs):
'''
Create a JDBC resource
'''
defaults = {
'description': '',
'enabled': True,
'id': name,
'poolName': '',
'target': 'server'
}
# Data = defaults + merge kwargs + poolname
data = defaults
data.update(kwargs)
if not data['poolName']:
raise CommandExecutionError('No pool name!')
return _create_element(name, 'resources/jdbc-resource', data, server)
def update_jdbc_resource(name, server=None, **kwargs):
'''
Update a JDBC resource
'''
# You're not supposed to update jndiName, if you do so, it will crash, silently
if 'jndiName' in kwargs:
del kwargs['jndiName']
return _update_element(name, 'resources/jdbc-resource', kwargs, server)
def delete_jdbc_resource(name, target='server', server=None):
'''
Delete a JDBC resource
'''
return _delete_element(name, 'resources/jdbc-resource', {'target': target}, server)
# System properties
def get_system_properties(server=None):
'''
Get system properties
'''
properties = {}
data = _api_get('system-properties', server)
# Get properties into a dict
if any(data['extraProperties']['systemProperties']):
for element in data['extraProperties']['systemProperties']:
properties[element['name']] = element['value']
return properties
return {}
def update_system_properties(data, server=None):
'''
Update system properties
'''
_api_post('system-properties', _clean_data(data), server)
return data
def delete_system_properties(name, server=None):
'''
Delete a system property
'''
_api_delete('system-properties/{0}'.format(name), None, server)