Source code for json_link.test.fault_inject

"""
A python module for GridLAB-D data (JSON) link handshaking (over UDP and TCP)

Classes:
    MasterLink
    SlaveLink

Implemented Methods:
  

Notes:

* Should work with Python 2.6+ and 3.x. Tested with 2.7.2 and 3.3
* Currently only supports IPv4
* The SLAVE will handle connections from any number of masters, BUT since the
  code is only single threaded each processing (including delay) are
  blocking. Hence, the SLAVE may miss packets if it is busy processing
  (or waiting to send) a different reply.
    
@author: Bryan Palmintier, NREL 2013
"""
# __future__ imports must occur at top of file. They enable using features from
# python 3.0 in older (2.x) versions are are ignored in newer (3.x) versions
# New print function
from __future__ import print_function
# Use Python 3.0 import semantics
from __future__ import absolute_import

__author__ = "Bryan Palmintier"
__copyright__ = "Copyright (c) 2013 Bryan Palmintier"
__license__ = """ 
@copyright: Copyright (c) 2013, Bryan Palmintier
@license: BSD 3-clause --
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:
 1) Redistributions of source code must retain the above copyright notice, this
    list of conditions and the following disclaimer.
 2) Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation 
    and/or other materials provided with the distribution.
 3) Neither the name of the National Renewable Energy Lab nor the names of its 
    contributors may be used to endorse or promote products derived from this 
    software without specific prior written permission.
    
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."""

## ===== History ====
#  [Current]             version     date       time     who     Comments
#                        -------  ----------    ----- ---------- ------------- 
__version__, __date__ = "0.9.2b","2013-04-29"  #22:20   BryanP   Move __init__ to _BaseLink. Master retry Init until slave ready
# [Older, in reverse order]
# version      date       time     who     Comments
# -------   ----------    ----- ---------- --------------------------------------- 
#  0.9.1b   2013-04-28    22:20   BryanP   Corrected slave timing & start/term sequencing. Rename gld_tcp_udp to raw_xchg with *Xchg*
#  0.9.0b   2013-04-27    02:00   BryanP   Full State Machine Functionality
#  0.1.0a   2013-04-25    17:20   BryanP   Initial Code

#Standard Library imports
import sys
import json
import timeit
import time
import datetime as dt
import random

#local imports
from ..xchg import raw_xchg

#===============================================================================
# Module level constants
#===============================================================================


#===============================================================================
# Error (Exception) Class definitions
#===============================================================================
[docs]class JsonLinkError(IOError): """Base class for exceptions in the gld_json_link module""" code = 2000
[docs]class JsonLinkTimeoutError(JsonLinkError): """Timeout waiting for reply""" code = JsonLinkError.code + 1
[docs]class JsonLinkNoDataError(JsonLinkError): """Expected data is empty""" code = JsonLinkError.code + 2
[docs]class JsonLinkBadWrapperError(JsonLinkError): """Malformed wrapper""" code = JsonLinkError.code + 3
[docs]class JsonLinkRemoteError(JsonLinkError): """received a (reasonably) well formed error packet from the remote machine""" code = JsonLinkError.code + 4
[docs]class JsonLinkOldMsgError(JsonLinkError): """Received a message with id older than a previously processed message""" code = JsonLinkError.code + 5
[docs]class JsonLinkActionMismatchError(JsonLinkError): """Incorrect handshaking order""" code = JsonLinkError.code + 6 #=============================================================================== # _BaseLink (module internal) #===============================================================================
class _BaseLink(object): """ Base class for JSON link subclasses""" #internal state machine state = 'beginState' in_schema = None out_schema = None #handshaking msg_num = 0 remote_msg_num = 0 response_str_out = None #override in subclass response_str_in = None #override in subclass retries = 0 #low level data exchange object _xchg = None #Options opt_verbose = True ignore_old = False ignore_dup = False #Track link statistics num_err = 0 num_tx = 0 num_rx = 0 #Internal process (Run) timing information _run_end_time = None run_timestep = 1.0 run_delay = 0.1 run_max = -1 run_count = 0 def __init__(self, run_timestep=1, run_delay=0.1, run_max=-1, **Xchg_args): """Constructor""" self.run_timestep = run_timestep self.run_delay = run_delay self.run_max = run_max self._setupOurXchg(**Xchg_args) def _setupOurXchg(self, **Xchg_args): """Must be implemented by subclasses to configure their raw exchange layer""" pass def go(self): """Method to beginState link execution""" pass def wrapJson(self, action, data, data_name='data'): """encapsulate data (typically a nested data dictionary) in side a JSON link wrapper""" #build message msg = {self.response_str_out:action} if data is not None: msg[data_name] = data msg['id'] = self.msg_num #convert to JSON string return json.dumps(msg) def unwrapJson(self, action, msg, data_name='data', ignore_msg_num=False): """check JSON link wrapper and decode data (typically a nested data dictionary)""" if not msg: raise JsonLinkNoDataError('No Data received') msg = json.loads(msg) if type(msg) is not dict: raise JsonLinkBadWrapperError('JSON Message is not a dictionary object') if 'error' in msg: err_msg = '' if 'params' in msg['error']: err_msg += msg['error']['params'] if 'code' in msg['error']: err_msg += " [code=%s]"%msg['error']['code'] if err_msg == '': err_msg = 'Remote Error Received, no message param or code' raise JsonLinkRemoteError(err_msg) if ignore_msg_num: if not 'id' in msg: raise JsonLinkBadWrapperError('Missing id field in JSON message') # Process id (sequence number) msg_id = int(msg['id']) if msg_id < self.remote_msg_num: if self.ignore_old: if self.opt_verbose: print('WARNING: Ignoring packet with old message id %d (expected %d)'%(msg_id, self.remote_msg_num + 1)) return None else: raise JsonLinkOldMsgError('A newer JSON message has been received (id=%d expected=%d)'%(msg_id, self.remote_msg_num + 1)) elif msg_id == self.remote_msg_num: if self.ignore_dup: if self.opt_verbose: print('WARNING: Ignoring duplicate message (id=%d)'%(msg_id)) return None else: raise JsonLinkOldMsgError('Duplicate message (id=%d)'%(msg_id)) self.remote_msg_num = msg_id #check action/response_str if self.response_str_in not in msg: raise JsonLinkBadWrapperError('Wrong type of JSON message (missing %s)'%(self.response_str_in)) if msg[self.response_str_in] != action: return (msg[self.response_str_in], None) if data_name is None: return (action, None) if data_name not in msg: raise JsonLinkBadWrapperError('JSON Message missing data payload (%s)'%(data_name)) return (action, msg[data_name]) def tx(self, msg, increment=True): """Transmit preformatted data message""" self._xchg.send(msg) if increment: self.msg_num += 1 def rx(self): """Wait for and receive data""" msg = self._xchg.receive() return msg def handleAltAction(self, action, msg, data_name='data', out_data=None, prompt=None): _actual_action, data = self.unwrapJson(action, msg, data_name, ignore_msg_num=True) if self.opt_verbose: if prompt is None: print('Rx %s: %s'%(action.upper(), data)) else: print(' %s%s'%(prompt,data)) msg = self.wrapJson(action, out_data, data_name) self.tx(msg) if self.opt_verbose: print('Tx %s: %s'%(action.upper(), out_data)) return data def sendMsg(self, action, data, data_name='data', raw_json=False, raw_packet=False): """Helper function to send general messages""" if raw_packet: self._xchg.send(data) return True elif raw_json: self.tx(data) else: msg = self.wrapJson(action, data, data_name) if self.opt_verbose: print('Tx %s %s: %s'%(action.upper(), data_name, data)) try: self.tx(msg) except raw_xchg.RawXchgCantSendError: return False return True #------- Simple Message functions. Designed to be overloaded for fault injection def sendInit(self, data=None): """Send INIT message""" return self.sendMsg('init', data, 'params') def sendInSchema(self, data=None): """Send INPUT message""" return self.sendMsg('input', data, 'schema') def sendOutSchema(self, data=None): """Send OUTPUT message""" return self.sendMsg('output', data, 'schema') def sendStart(self, data=None): """Send START message""" return self.sendMsg('start', data) def sendSync(self, data=None): """Send SYNC message""" return self.sendMsg('sync', data) def sendTerm(self, data=None): """Send TERM message""" return self.sendMsg('term', data) def runWaitForTimeStep(self): while timeit.default_timer() < self._run_end_time: time.sleep(self.run_timestep/1000) #Find next time period end. self._run_end_time += self.run_timestep #Ensure that the next time end is in the future and warn if missed step while timeit.default_timer() > self._run_end_time: if self.opt_verbose: print('Oops: that run step was too long. Skipping a step to resync') self._run_end_time += self.run_timestep def endState(self, old_state): """endState state""" if self.opt_verbose: print('\nEND\n') exit() #=============================================================================== # MasterLink #===============================================================================
[docs]def dummyData(schema): """Build up dummy json data package based on the provided schema""" data = dict() if type(schema) is dict: for field in schema: f_type = schema[field].split()[0] if f_type == 'timestamp': data[field] = dt.datetime.now().isoformat(' ') else: data[field] = random.random() return data