Пример #1
0
  beforeEach(function(){
    log = Logger.getLogger('FrontendSpec');
    spyOn(Logger, 'getLogger').andReturn(log);

    jasmine.Clock.useMock();

    config = {
      frontend: 'tcp://127.0.0.1:5560',
      smi: {
        heartbeat: 1000,
        maxTTL: 1500,
        updateInterval: 100
      }
    };

    socketMock = {
      send: Function.apply(),
      bindSync: Function.apply(),
      on: Function.apply(),
      close: Function.apply()
    };

    smi = new SMI(config.smi);
    target = new Frontend(config, smi);
  });
  beforeEach(function(){
    log = Logger.getLogger('Broker');
    spyOn(Logger, 'getLogger').andReturn(log);

    jasmine.Clock.useMock();

    config = {
      version: '0.0'
    };
    frontendMock = {
      run: Function.apply(),
      stop: Function.apply()
    };
    backendMock = {
      run: Function.apply(),
      stop: Function.apply()
    };
    smiMock = {
      run: Function.apply(),
      stop: Function.apply()
    };
    target = new Broker(config, smiMock, frontendMock, backendMock);
  });
Пример #3
0
  var Backend = function(configuration, smiService){

    var log = Logger.getLogger('Backend');

    var defaults = {
      backend: 'tcp://127.0.0.1:5559'
    };

    var config = _.defaults(configuration, defaults);
    var socket;

    var smiActions = _(['UP','DOWN','HEARTBEAT']);

    var reply = function(message){
      message.type = Message.Type.REP;

      log.info("backend reply to: %s rid: %s with status: %s",
        message.identity, message.rid, message.status);

      // TODO: log frames in debug mode
      // if(log.isDebug()){
      //   log.debug(message.toString());
      // }

      socket.send(message.toFrames());
    };

    var forward = function(frames){
      // on forward the frist frames is destination
      frames[TYPE_FRAME + 1] = Message.Type.REQ;

      log.info("backend forward request from: %s rid: %s to: %s",
        frames[IDENTITY_FRAME + 1], frames[RID_FRAME + 1], frames[IDENTITY_FRAME]);

      // TODO: log frames in debug mode
      // if(log.isDebug()){
      //   var message = Message.parse(frames);
      //   log.debug(message.toString());
      // }

      socket.send(frames);
    };

    var replyError = function(errorCode, message){
      var error = errors[errorCode.toString()];

      message.status = error.code;
      message.payload = error.body;

      reply(message);
    };

    var getSMIAction = function(address){
      var verb = address.verb.toUpperCase();

      if(!smiActions.contains(verb)){
        return null;
      }

      return smiService[verb.toLowerCase()];
    };

    var handleRequest = function(frames) {
      var message = Message.parse(frames);

      var isSMIRequest = message.address.sid === 'SMI';
      if(!isSMIRequest){
        log.error("backend doesn't respond to %s", message.address.sid);
      }

      var action = getSMIAction(message.address);
      if(action === null){
        log.error("SMI service doesn't respond to %s", message.address.verb);
      }

      if (isSMIRequest && action !== null){
        log.debug("backend routing from: %s rid: %s to SMI.%s request...",
          message.identity, message.rid, message.address.verb);
        message = action(message);
        if(message.status === 200) {
          reply(message);
          return;
        }

      } else {
        message.status = 500;
      }

      replyError(message.status, message);
    };

    var onMessage = function(){
      var frames = _.toArray(arguments);

      var from;
      // check if it is reply
      if(frames.length === REPLY_FRAME_SIZE){
        // remove worker identity and let client identity on position 0
        from = frames.shift();
      }

      log.info("backend received from: %s rid: %s",
        from || frames[IDENTITY_FRAME], frames[RID_FRAME]);

      // TODO: log frames in debug mode
      // if(log.isDebug()) {
      //   var msg = Message.parse(frames);
      //   log.debug(msg.toString());
      // }

      var isRequest = frames[TYPE_FRAME].toString('utf8') === Message.Type.REQ;
      if (isRequest) {
        handleRequest(frames);
        return;
      }

      var to = frames[IDENTITY_FRAME];
      if(!to){
        log.error("backend invalid routing from: %s to: %s", from, to);
        return;
      }

      var status = frames[STATUS_FRAME];
      log.info("backend routing from: %s to: %s sending: %s", from, to, status);

      this.frontendSendCallback(frames);
    };

    var onError = function(error){
      // reply with error
      log.error("backend received zmq error => %s", error.stack);
    };

    // public methods

    this.frontendSendCallback = null;

    this.send = forward.bind(this);

    this.run = function(){
      log.info('  Backend connected to %s', config.backend);

      socket = zmq.socket('router');
      socket.on('message', onMessage.bind(this));
      socket.on('error', onError.bind(this));
      socket.bindSync(config.backend);
    };

    this.stop = function(){
      log.info('  Backend disconnected from %s', config.backend);
      socket.close();
    };

  };
Пример #4
0
  var ZMQService = function(configuration){

    var log = Logger.getLogger('ZMQService');

    var defaults = {
      broker: 'tcp://127.0.0.1:5560',
      heartbeat: 1000,
      sid: null
    };

    var config = _.defaults(configuration, defaults);

    var identity = config.sid + "#" + uuid.v1();
    var socket = zmq.socket('dealer');
    var heartbeatIntervalObj;
    var socketClose = false;

    var onError = function(error){
      // reply with error
      log.error("Received zmq error => %s", error.stack);
    };

    var reply = function(message){
      message.type = Message.Type.REP;

      log.info("Reply to: %s with status: %s", message.identity, message.status);
      log.debug(message.toString());

      socket.send(message.toFrames());
    };

    var replyError = function(errorCode, message){
      var error = errors[errorCode.toString()];

      message.status = error.code;
      message.payload = error.body;

      reply(message);
    };

    var sendRequest = function(message){
      message.identity = identity;

      log.info("Sending %s to %s:%s",
        message.identity, message.address.sid, message.address.verb);
      log.debug(message.toString());

      socket.send(message.toFrames());
    };

    var onMessage = function(){
      var frames = _.toArray(arguments);
      var msg = Message.parse(frames);

      log.info("Received %s from: %s to: %s:%s",
        msg.type, msg.identity, msg.address.sid, msg.address.verb);

      log.debug(msg.toString());

      if(msg.type === Message.Type.REP) {
        log.info("Reply received from %s:%s with status %s",
          msg.address.sid, msg.address.verb, msg.status);

        // if is down message
        socketClose = (msg.address.sid === "SMI" && msg.address.verb === "DOWN");

        return;
      }

      var verb = this[msg.address.verb];
      var isValidAction = msg.address.sid === config.sid && verb != null;
      if(!isValidAction){
        // reply with error
        log.error("Invalid address => %j", msg.address);
        replyError(404, msg);
        return;
      }

      log.debug("Message routed to %s...", msg.address.verb);

      try {
        // TODO: replace sync execution by eventfull
        // check this http://stackoverflow.com/questions/7310521/node-js-best-practice-exception-handling
        msg.payload = verb(msg.payload, msg);
        // reply with success message
        msg.status = 200;
        reply(msg);
      }
      catch(error){
        // log.error("blow log");
        replyError(500, msg);
      }
    };

    // public methods

    this.run = function(){
      socket.identity = identity;
      socket.linger = 0;
      socket.on('message', onMessage.bind(this));
      socket.on('error', onError.bind(this));
      socket.connect(config.broker);

      log.info('%s connected to %s', identity, config.broker);

      // send announcement message
      var msg = new Message("SMI", "UP");
      msg.payload = config.sid;
      sendRequest(msg);

      // register heartbeat send
      heartbeatIntervalObj = setInterval(function() {
        var msg = new Message("SMI", "HEARTBEAT");
        msg.payload = config.sid;
        sendRequest(msg);
      }, config.heartbeat);
    };

    this.stop = function(){
      log.info('%s disconnecting from %s', identity, config.broker);

      // clear interval
      clearInterval(heartbeatIntervalObj);

      var msg = new Message("SMI", "DOWN");
      msg.payload = config.sid;
      sendRequest(msg);

      // wait for message reply
      var stoping = function() {

        if(socketClose){
          log.info('%s disconnected from %s', identity, config.broker);
          socket.close();
        }
        else {
          setTimeout(stoping, 500);
        }
      };
      stoping();
    };
  };
var ServiceManagementInterface = function(configuration){

  var log = Logger.getLogger('SMI');

  var defaults = {
    // heartbeat interval in ms
    heartbeat: 1000,
    // max ttl for a service in ms
    maxTTL: 1500,
    // refresh service ttl update interval in ms
    updateInterval: 100
  };
  var config = _.defaults(configuration, defaults);

  // dictionary with sid as key and ServiceInfo array as value
  var services = { };

  // store refresh interval object id
  var onRefreshServicesTTLIntervalObj;

  // load balancing strategy
  var strategy = new RoundRobinStrategy();

  // this class is used to represent service instance info
  var ServiceInfo = function(sid, identity){
    this.sid = sid;
    this.identity = identity;
    this.ttl = config.maxTTL;

    ServiceInfo.prototype.refreshTTL = function(){
      var newTTL = this.ttl + config.heartbeat + config.updateInterval;
      this.ttl = newTTL > config.maxTTL ? config.maxTTL : newTTL;
    };

    ServiceInfo.prototype.decrementTTL = function(){
      this.ttl = this.ttl - config.updateInterval;
      return this.ttl;
    };

    ServiceInfo.prototype.isDead = function(){
      return this.ttl < 1;
    };

    ServiceInfo.prototype.toString = function(){
      return util.format("identity: %s TTL: %sms", this.identity, this.ttl);
    };
  };

  var getInstances = function(sid){
    if (!services[sid]){
      services[sid] = [];
    }

    return services[sid];
  };

  var onRefreshServicesTTL = function() {
    log.trace("SMI refreshing services TTL...");

    var decrementAndRemove = function(service){
      service.decrementTTL();
      var isDead = service.isDead();

      if (isDead) {
        log.info("SMI TTL unregister for sid: %s instance: %s!", service.sid, service.identity);
      }

      return isDead;
    };

    for(var sid in services){
      _.remove(services[sid], decrementAndRemove);
    }

    var logInstance = function(instance){
      log.trace("\t\t%s\n", instance.toString());
    };

    log.trace("SMI Service instances:");
    for(var id in services){
      log.trace("\t%s instances", id);
      services[id].forEach(logInstance);
    }
  };

  // public methods

  this.up = function(msg) {
    var to = msg.payload.toUpperCase();
    var instances = getInstances(to);
    var isRegistered = _.some(instances, { identity: msg.identity });

    if(isRegistered){
      log.warn("SMI the service instance %s is already registered!", msg.identity);

      msg.status = 500;
      return msg;
    }

    instances.push(new ServiceInfo(to, msg.identity));

    log.info("SMI register for sid: %s instance: %s!", to, msg.identity);

    msg.status = 200;
    return msg;
  };

  this.down = function(msg){
    var to = msg.payload.toUpperCase();
    var instances = getInstances(to);
    var instanceIndex = _.findIndex(instances, { identity: msg.identity });

    if(instanceIndex === -1){
      log.warn("SMI the service instance %s is not registered!", msg.identity);

      msg.status = 500;
      return msg;
    }

    instances.splice(instanceIndex, 1);

    log.info("SMI unregister for sid: %s instance %s!", to, msg.identity);

    msg.status = 200;
    return msg;
  };

  this.heartbeat = function(msg){
    var to = msg.payload.toUpperCase();
    var instances = getInstances(to);
    var instance = _.find(instances, { identity: msg.identity });

    if(!instance){
      return this.up(msg);
    }

    instance.refreshTTL();

    log.trace("SMI refreshed service instance %s TTL!", msg.identity);

    msg.status = 200;
    return msg;
  }.bind(this);

  this.getInstance = function(sid){
    var instances = getInstances(sid.toUpperCase());
    var instance = strategy.next(sid.toUpperCase(), instances);
    return instance ? instance.identity : null;
  };

  this.run = function(){
    log.debug("SMI Starting refesh routine to execute every %sms", config.updateInterval);
    onRefreshServicesTTLIntervalObj = setInterval(onRefreshServicesTTL.bind(this), config.updateInterval);

    log.info("started...");
  };

  this.stop = function(){
    log.debug("SMI Stoping refesh routine");
    clearInterval(onRefreshServicesTTLIntervalObj);
    onRefreshServicesTTLIntervalObj = null;

    log.info("stoped...");
  };
};
var _ = require('lodash');
var moment = require('moment');
var Logger = require('logger-facade-nodejs');
var log = Logger.getLogger('micro.metric.service');

// Add Test: Add proper test to ensure the format of the metric, mocking logger was giving troubles
function metric(name, val, message) {
  // val is not a NaN should log the metric
  if (isNaN(val)) { return message; }

  var metadata = _.pick(message, ['type', 'rid', 'address', 'status', 'client', 'clientId', 'transaction']);
  metadata['micrometric'] = { name: name, value: val }
  log.info(metadata, 'Publishing metric "%s":%sms', name, val);
  return metadata;
}

function recordTimestamp(message, name) {
  var now = moment().valueOf();

  // add the micro service receive timestamp
  var ts = {};
  ts[name] = now;
  message.headers = _.defaults(message.headers, ts);
  metric(name, now, message);

  return now;
}

function start(message) {
  var now = recordTimestamp(message, 'micro.sr');