import logging
import traceback
import libsolace.settingsloader as settings
import libsolace
from libsolace.SolaceXMLBuilder import SolaceXMLBuilder
from libsolace.SolaceCommandQueue import SolaceCommandQueue
from libsolace import xml2dict
from libsolace.plugin import PluginResponse
try:
from collections import OrderedDict
except:
from ordereddict import OrderedDict
try:
import simplejson
except:
from json import simplejson
from libsolace.util import httpRequest, generateRequestHeaders, generateBasicAuthHeader
[docs]class SolaceAPI:
"""
Connects to a Solace cluster's *primary* and *backup* appliance(s)
a SolaceAPI instance contains a SolaceXMLBuilder and a SolaceCommandQueue
in order to facilitate the generation of SEMP XML requests, enqueuing the XML
requests, and sending them to the appliance(s) through the rpc(str) method.
SolaceAPI connects to **both** appliances in a redundant pair setup and gets
the the *primary* and *backup* node states. Typically you issue the same SEMP
command to both appliances. Commands can also be issued to either the primary
or the backup appliance utilizing the `primaryOnly` and `backupOnly` kwargs.
see: :func:`~libsolace.SolaceAPI.SolaceAPI.rpc`
The version of the SolOS-TR OS is detected on automatically, and this behaviour
can be overridden with the `version` kwarg. If using the VMR you will want to
pass in both detect_status=False and version="soltr/7_1_1".
:keyword environment: the environemnt
:type environment: str
:keyword detect_status: detection of node primary/backup status, pass True
here for the VMR or single appliances.
:type detect_status: bool
:keyword version: override appliance version detection. Some versions of SolOS-TR
require you to set the language level a bit higher like the VMR for example.
:type version: str
:keyword testmode: Tells the api to connect using the READ_ONLY_USER as defined
in the libsolace.yaml file.
:type testmode: bool
:rtype: SolaceAPI.SolaceAPI
:returns: instance
Examples:
>>> from libsolace.SolaceXMLBuilder import SolaceXMLBuilder
>>> api = SolaceAPI("dev")
>>> api.x = SolaceXMLBuilder("LOG: Showing the Message Spool in %s" % api.environment, version=api.version)
>>> api.x.show.message_spool.detail
OrderedDict()
>>> response = api.rpc(api.x)
Setting the API version if detection fails
>>> from libsolace.SolaceXMLBuilder import SolaceXMLBuilder
>>> api = SolaceAPI("dev", version="soltr/7_1_1")
>>> api.x = SolaceXMLBuilder("My description of what im doing", version=api.version)
>>> api.x.show.message_spool
OrderedDict()
>>> response = api.rpc(api.x, primaryOnly=True)
>>> response[0]['rpc-reply']['rpc']['show']['message-spool']['message-spool-info']['config-status']
u'Enabled (Primary)'
Query the backup appliance only
>>> from libsolace.SolaceXMLBuilder import SolaceXMLBuilder
>>> api = SolaceAPI("dev", detect_status=False)
>>> api.x = SolaceXMLBuilder("My Something something", version=api.version)
>>> api.x.show.version
OrderedDict()
>>> r = api.rpc(api.x, backupOnly=True)
>>> # check the response was the "configured" backup
>>> r[0]['HOST'] == settings.SOLACE_CONF["dev"]['MGMT'][1]
True
Get a instance of some plugin from the plugin manager
>>> api = SolaceAPI("dev")
>>> type(api.manage("NullPlugin"))
<class 'libsolace.items.NullPlugin.NullPlugin'>
"""
def __init__(self, environment, version=None, detect_status=True, testmode=False, **kwargs):
try:
logging.info("Solace Client SEMP version: %s" % version)
self.version = version
logging.info("Connecting to appliances %s in %s" % (settings.SOLACE_CONF[environment]['MGMT'], environment))
self.environment = environment
self.settings = settings
self.config = settings.SOLACE_CONF[environment]
logging.debug("Loaded Config: %s" % self.config)
# testmode sets the user to the RO user
self.testmode = testmode
if self.testmode:
self.config['USER'] = settings.READ_ONLY_USER
self.config['PASS'] = settings.READ_ONLY_PASS
logging.info('READONLY mode')
# for SSL / TLS
if 'VERIFY_SSL' not in self.config:
self.config['VERIFY_SSL'] = True
# detect primary / backup node instance states or assume
# 1st node is primary and second is backup
self.detect_status = detect_status
if self.detect_status:
logging.info("Detecting primary and backup node states")
self.status = self.get_message_spool(**kwargs)
self.primaryRouter = None
self.backupRouter = None
for node in self.status:
result = self.__detect_state(node)
if result == 'Primary':
self.primaryRouter = node['HOST']
elif result == 'Backup':
self.backupRouter = node['HOST']
if self.primaryRouter is None:
raise Exception("Failed to detect primary router")
if self.backupRouter is None:
raise Exception("Failed to detect backup router")
if self.primaryRouter == self.backupRouter:
# impossible to test, but possible to happen...
raise Exception("Error, detected router %s to be both primary and backup" % self.primaryRouter)
logging.info("Detected primary Router: %s" % self.primaryRouter)
logging.info("Detected backup Router: %s" % self.backupRouter)
else:
logging.info("Not detecting statuses, using config")
try:
self.primaryRouter = self.config['MGMT'][0]
except Exception, e:
logging.error("No routers")
raise
try:
self.backupRouter = self.config['MGMT'][1]
except IndexError, e:
logging.warn("No second router in config")
kwargs["primaryOnly"] = True
kwargs["backupOnly"] = False
pass
# if the version is NOT specified, query appliance versions
# assumes that backup and primary are SAME firmware version.s
if version == None:
logging.debug("Detecting Version")
self.xmlbuilder = SolaceXMLBuilder("Detecting SolOS-TR Version", version="soltr/5_0")
self.xmlbuilder.show.version
result = self.rpc(str(self.xmlbuilder), **kwargs)
self.version = result[0]['rpc-reply']['@semp-version']
else:
logging.info("Override SolOS-TR Version: %s" % version)
self.version = version
logging.info("SolOS-TR Version: %s" % self.version)
# backwards compatibility
# self.xmlbuilder = SolaceXMLBuilder(version=self.version)
# shortcut / new methods
self.x = SolaceXMLBuilder("XML Buider", version=self.version)
self.cq = SolaceCommandQueue(version=self.version)
except Exception, e:
logging.warn("Solace Error %s" % e)
raise
def __restcall(self, request, primaryOnly=False, backupOnly=False, **kwargs):
logging.info("%s user requesting: %s kwargs:%s primaryOnly:%s backupOnly:%s"
% (self.config['USER'], request, kwargs, primaryOnly, backupOnly))
self.kwargs = kwargs
# appliances in the query
# appliances = self.config['MGMT']
# appliances in order, fallback to unordered if this is the early calls to determine order
try:
appliances = [self.primaryRouter, self.backupRouter]
except AttributeError, e:
appliances = self.config['MGMT']
# change appliances based on boolean conditions
if len(appliances) > 1:
if primaryOnly and backupOnly:
appliances = [self.primaryRouter, self.backupRouter]
logging.info("Forced Both: %s, request: %s" % (appliances, request))
elif primaryOnly and not backupOnly:
appliances = [self.primaryRouter]
logging.info("Primary: %s, request: %s" % (appliances, request))
elif backupOnly and not primaryOnly:
appliances = [self.backupRouter]
logging.info("Backup: %s, request: %s" % (appliances, request))
else:
logging.info("Both: %s, request: %s" % (appliances, request))
else:
logging.warn("Only one appliance in config!!!")
appliances = [self.primaryRouter]
try:
data = OrderedDict()
codes = OrderedDict()
for host in appliances:
logging.debug("Querying host: %s" % host)
url = host
request_headers = generateRequestHeaders(
default_headers={
'Content-type': 'text/xml',
'Accept': 'text/xml'
},
auth_headers=generateBasicAuthHeader(self.config['USER'], self.config['PASS'])
)
logging.debug("request_headers: %s" % request_headers)
(response, response_headers, code) = httpRequest(url, method='POST', headers=request_headers,
fields=request, timeout=5000,
verifySsl=self.config['VERIFY_SSL'])
logging.debug("response: %s" % response)
data[host] = response
logging.debug("code: %s" % code)
codes[host] = code
logging.debug(data)
for k in data:
thisreply = None
try:
thisreply = xml2dict.parse(data[k])
if thisreply['rpc-reply'].has_key('execute-result'):
if thisreply['rpc-reply']['execute-result']['@code'] != 'ok':
logging.warn("Device: %s: %s %s" % (k, thisreply['rpc-reply']['execute-result']['@code'],
"Request that failed: %s" % request))
logging.warn("Device: %s: %s: %s" % (k, thisreply['rpc-reply']['execute-result']['@code'],
"Reply from appliance: %s" %
thisreply['rpc-reply']['execute-result']['@reason']))
else:
logging.debug("Device: %s: %s" % (k, thisreply['rpc-reply']['execute-result']['@code']))
logging.debug("Device: %s: %s" % (k, thisreply))
else:
logging.debug("no execute-result in response. Device: %s" % k)
except Exception, e:
logging.error("Error decoding response from appliance")
logging.error("Response Codes: %s" % codes)
logging.error("Data Object: key: %s, data: %s " % (k, data))
raise # (Exception("Appliance Communication Failure"))
logging.debug("Returning Data from rest_call")
return data, codes
except Exception, e:
traceback.print_exc()
logging.warn("Solace Error %s" % e)
raise
[docs] def get_redundancy(self):
"""
Return redundancy status
Example:
>>> api = SolaceAPI("dev")
>>> api.get_redundancy()[0]['rpc-reply']['rpc']['show']['redundancy']['config-status']
u'Enabled'
"""
request = SolaceXMLBuilder(version=self.version)
request.show.redundancy
return self.rpc(str(request))
[docs] def get_memory(self):
""" Returns the Memory Usage
Example of request XML
<rpc semp-version="soltr/6_0">
<show>
<memory></memory>
</show>
</rpc>
"""
request = SolaceXMLBuilder(version=self.version)
request.show.memory
return self.rpc(str(request))
[docs] def get_message_spool(self, **kwargs):
""" show message spool
:param version:
"""
request = SolaceXMLBuilder("Getting message spool status", version=self.version)
request.show.message_spool
return self.rpc(str(request), **kwargs)
def __detect_state(self, response):
""" TODO: is this sufficient to detect cluster state? """
message_spool = response['rpc-reply']['rpc']['show']['message-spool']['message-spool-info']
if message_spool['operational-status'] == 'AD-Active':
return 'Primary'
elif message_spool['operational-status'] == 'AD-Standby':
return 'Backup'
else:
raise Exception("Unknown message-spool operational-status '%s'" % message_spool['operational-status'])
[docs] def rpc(self, xml, allowfail=False, primaryOnly=False, backupOnly=False, xml_response=False, **kwargs):
"""
Execute a SEMP command on the appliance(s), call with a string representation
of a SolaceXMLBuilder instance.
Args:
xml(str): string representation of a SolaceXMLBuilder instance.
allowFail(Optional(bool)): tollerate some types of errors from the
appliance.
primaryOnly(Optional(bool)): only execute on primary appliance.
backupOnly(Optional(bool)): only execute on backup appliance.
Returns:
data response list as from appliances. Json-like data
Example:
>>> from libsolace.SolaceXMLBuilder import SolaceXMLBuilder
>>> conn = SolaceAPI("dev")
>>> conn.x = SolaceXMLBuilder(version = conn.version)
>>> conn.x.show.version
OrderedDict()
>>> type(conn.rpc(str(conn.x)))
<type 'list'>
"""
logging.debug(type(xml))
if type(xml) == type(None):
logging.warn("Ignoring empty request")
return
elif isinstance(xml, PluginResponse):
logging.info("Plugin Response")
kwargs = xml.kwargs
xml = xml.xml
elif type(xml) == type(()):
kwargs = xml[1]
xml = xml[0]
elif isinstance(xml, str):
pass
elif isinstance(xml, SolaceXMLBuilder):
xml = str(xml)
else:
logging.warn("I dont recognize this type of rpc: %s type: %s" % (xml, type(xml)))
raise Exception("Not a valid RPC argument")
responses = None
mywargs = kwargs
logging.debug("Kwargs: %s" % mywargs)
logging.info("Request SEMP: %s" % xml)
logging.debug("primaryOnly: %s" % primaryOnly)
logging.debug("backupOnly: %s" % backupOnly)
if "primaryOnly" in mywargs:
primaryOnly = mywargs.pop("primaryOnly")
if "backupOnly" in mywargs:
backupOnly = mywargs.pop("backupOnly")
try:
data = []
responses, codes = self.__restcall(xml, primaryOnly=primaryOnly, backupOnly=backupOnly, **mywargs)
if xml_response:
return responses
for k in responses:
response = xml2dict.parse(responses[k])
logging.debug("Response: %s" % response)
response['HOST'] = k
if not allowfail:
if 'parse-error' in response['rpc-reply']:
raise Exception(str(response))
elif 'permission-error' in response['rpc-reply']:
if self.testmode:
logging.debug('tolerable permission error in test mode')
else:
logging.critical("Error occured, request was: %s" % xml)
raise Exception(str(response))
else:
data.append(response)
else:
data.append(response)
if len(data) is 1:
data.append(None)
return data
except:
logging.error("responses: %s" % responses)
raise
[docs] def manage(self, plugin_name, **kwargs):
"""
Gets a plugin, configures it, then allows direct communication with it.
Plugins are passed the kwargs directly if any are specified.
Example:
>>> api = SolaceAPI("dev")
>>> p1 = api.manage("NullPlugin")
>>> p1.some_method("foo", bar="baz")
(('foo',), {'bar': 'baz'})
>>> p2 = api.manage("NullPlugin", a="a")
>>> p2.kwargs['a']
'a'
"""
plugin = libsolace.plugin_registry(plugin_name, **kwargs)
logging.debug("Setting up the plugin instance with api and kwargs")
return plugin(api=self, **kwargs)