<|start_filename|>lib/parser.js<|end_filename|>
'use strict';

/**
 * Automatic parser interface for configuration changes.
 *
 * @constructor
 * @param {Configuration} config The parent configuration instance
 * @api private
 */
function Parser(config) {
  if (!(this instanceof Parser)) return new Parser(config);

  this.config = config;

  this.config.on('read', function read(type) {
    this[type]();
  }.bind(this));
}

/**
 * Parse files with a `.json` extension.
 *
 * @returns {Parser}
 */
Parser.prototype.json = function json() {
  this.config.data = JSON.parse(this.config.source);

  return this;
};

/**
 * Parses files with a `.cfg` extension.
 *
 * @returns {Parser}
 */
Parser.prototype.cfg = function cfg() {
  var has = this.config.definitions.has
    , data = this.config.source
    , section
    , current
    , name
    , hash
    , key;

  data.split(/\n/).forEach(function parse(line) {
    line = line.trim();

    // No content on line just return.
    if (!line.length) return;

    // Keep track of the section.
    section = this.config.definitions.sections.filter(has.bind(this, line))[0];

    if (section) {
      name = line.split(' ')[1]; // Named section support.
      return current = section;
    }

    // Check content against known keys in current section.
    key = this.config.definitions.allowed[current].filter(has.bind(this, line))[0];

    if (!key || !key.length) return;

    // Store the value if we got a key match, and add the comment (if present).
    hash = line.split('#');
    this.config[current](name).add(key, hash[0].substr(key.length).trim())(hash[1]);
  }, this);

  return this;
};

//
// Expose the module.
//
module.exports = Parser;


<|start_filename|>lib/configuration.js<|end_filename|>
'use strict';

var EventEmitter = require('events').EventEmitter
  , Orchestrator = require('./orchestrator.js')
  , Composer = require('./composer.js')
  , Parser = require('./parser.js')
  , path = require('path')
  , util = require('util')
  , fs = require('fs');

//
// Empty function
//
function noop(){}

function Configuration() {
  if (!(this instanceof Configuration)) return new Configuration();
  EventEmitter.call(this);

  //
  //  Required for parsing and composing the configurations.
  //
  this.data = Object.create(null);
  this.source = '';

  //
  // The actual parsers and configurators.
  //
  this.composer = new Composer(this);
  this.parser = new Parser(this);
  this.definitions = Configuration;
}

util.inherits(Configuration, EventEmitter);

/**
 * Read out a configuration file and parse it for conversion.
 *
 * @param {String} location The path to the configuration file.
 * @param {Function} fn Callback
 * @returns {Configuration}
 */
Configuration.prototype.read = function read(location, fn) {
  var type = path.extname(location).substr(1)
    , self = this
    , source;

  fn = fn || noop;

  if (!(type in this.parser)) {
    fn.call(this, new Error('Invalid file, no parser for this extension'));
    return this;
  }

  //
  // Set the `source` and emit the `read` event with file extension so it can
  // start with parsing the original file's source. After everything is parsed
  // we can call the supplied callback which can process things further.
  //

  if (!fn) {
    source = fs.readFileSync(location, 'utf-8');

    this.reset();
    this.source = source;
    this.emit('read', type);

    return this;
  }

  fs.readFile(location, 'utf-8', function read(err, config) {
    if (err) return fn(err);

    self.reset();
    self.source = config;
    self.emit('read', type);

    fn(err, config);
  });

  return this;
};

/**
 * Write the config to file, composer type is aquired from file extension.
 *
 * @param {String} location file location
 * @param {Function} fn
 * @api public
 */
Configuration.prototype.write = function write(location, fn) {
  var type = path.extname(location).substr(1)
    , data;

  //
  // Check if the given extension is supported by our configuration generator.
  // Default to cfg as that's the format that HAProxy supports.
  //
  if (!(type in this.composer)) type = 'cfg';
  data = this.composer[type]();

  if (!fn) fs.writeFileSync(location, data);
  else fs.writeFile(location, data, fn);

  return this;
};

/**
 * Verify the current config by using HAProxies check.
 *
 * @param {Function} fn
 * @api public
 */
Configuration.prototype.verify = function verify(fn) {
  var tmp = '/tmp/haproxy.'+ Math.random().toString(36).substring(2).toUpperCase()
    , orchestrator = new Orchestrator({ config: tmp })
    , data = this.composer.cfg();

  fs.writeFile(tmp, data, function hollaback(err) {
    if (err) return fn(err);

    orchestrator.verify(function verification() {
      fn.apply(exports, arguments);

      //
      // We don't care if it fails or not, each file would have a unique name.
      // And we don't really need to remove it from the file system but it's
      // just `nice` do some additional cleanup
      //
      fs.unlink(tmp, function likeigiveafuck() {});
    });
  });

  return this;
};

/**
 * Get value of the section-key combination.
 *
 * @param {String} section predefined section
 * @param {String} key
 * @return {String} key value
 * @api private
 */
Configuration.prototype.get = function get(section, name, key) {
  switch (arguments.length) {
    case 1:
      return this.data[section];
    case 2:
      return this.data[section][name];
    default:
      return this.data[section][name][key];
  }
};

/**
 * Set the section-key combination to value.
 *
 * @param {String} section predefined section
 * @param {String} key
 * @param {String} value
 * @return {Object} bind comment to key.
 * @api private
 */
Configuration.prototype.set = function set(section, name, key, value) {
  var element, sub;

  // Check if the current key is allowed to be set on the section.
  if (!~this.definitions.allowed[section].indexOf(key)) return this;

  this.data[section] = element = this.data[section] || Object.create(null);
  element[name] = sub = element[name] || Object.create(null);
  sub[key] = value;

  // Expose comment function bound to key.
  return this.comment.bind(this, section, name, key);
};

/**
 * Adds content to key, transforms value to array if required
 *
 * @param {String} section predefined section
 * @param {String} key
 * @param {String} value
 * @return {Object} bind comment to key.
 * @api private
 */
Configuration.prototype.add = function add(section, name, key, value) {
  var element, sub;

  // Check if the current key is allowed to be set on the section.
  if (!~this.definitions.allowed[section].indexOf(key)) return this;

  this.data[section] = element = this.data[section] || Object.create(null);
  element[name] = sub = element[name] || Object.create(null);

  // If this key is undefined just call set.
  if (!sub[key]) return this.set(section, name, key, value);

  // Convert to array so we can just push to it.
  if (sub[key] && typeof sub[key] === 'string') sub[key] = [sub[key]];

  // Add the value
  sub[key].push(value);

  // Expose comment function bound to key.
  return this.comment.bind(this, section, key);
};

/**
 * Add comment to section-key combinations. General section comments are added
 * to `commentary.pre`. Return early if there is no actual text.
 *
 * @param {String} section predefined section
 * @param {String} key
 * @param {String} text
 * @return {String} text
 * @api private
 */
Configuration.prototype.comment = function comment(section, name, key, text) {
  var element, sub;

  // Do add comments, will you!
  if (!text) return this;

  this.data[section] = element = this.data[section] || Object.create(null);
  element[name] = sub = element[name] || Object.create(null);
  sub.commentary = sub.commentary || Object.create(null);
  sub.commentary[key] = text.trim();

  return sub.commentary[key];
};

/**
 * Reset and clear the current config
 *
 * @api public
 */
Configuration.prototype.reset = function reset() {
  this.data = Object.create(null);
  this.source = '';

  return this;
};

/**
 * Config sections and bitmasks.
 *
 * @type {Object}
 * @api private
 */
Configuration.masks = {
  global: 1 << 1,    // 2
  defaults: 1 << 2,  // 4
  frontend: 1 << 3,  // 8
  listen: 1 << 4,    // 16
  backend: 1 << 5,   // 32
  userlist: 1 << 6   // 64
};

/**
 * Configuration keys and bitwise value, each bitwise value appoints
 * sections to which the key belongs.
 *
 * @type {Object}
 * @api private
 */
Configuration.keys = require('./config');

/**
 * Change config strings to suitable function names.
 *
 * @param {String} value function name
 * @return {String}
 * @api private
 */
Configuration.functionalize = function functionalize(value) {
  return value.toLowerCase().replace(/[^a-z]/g, '');
};

/**
 * Is config key the first part of the line?
 *
 * @param {String} line of content
 * @param {String} key in config
 * @return {Boolean}
 * @api private
 */
Configuration.has = function has(line, key) {
  return line.indexOf(key) === 0;
};

//
// Array of possible configuration sections.
//
Configuration.sections = Object.keys(Configuration.masks);

//
// An object with the allowed sections & configuration value mapping.
//
Configuration.allowed = Object.create(null);

//
// Generate allowed config keys per section from the bitmasks.
//
Configuration.sections.forEach(function prepareKeys(section) {
  var mask = Configuration.masks[section]
    , current;

  Object.keys(Configuration.keys).forEach(function bitmask(bit) {
    current = Configuration.allowed[section] || [];

    if (mask & +bit) {
      Configuration.allowed[section] = current.concat(Configuration.keys[bit]);
    }
  });
});

//
// Generate some helper methods on each section to quickly set and get values.
//
Configuration.sections.forEach(function prepareFunctions(section) {
  if (section in Configuration.prototype) throw new Error('Duplicate method '+ section);

  Configuration.prototype[section] = function setup(name) {
    //
    // Defaults have no name parameter, but for eaz and consistency we
    // are gonna pretend it has a general name section.
    //
    if (!name || section === 'defaults') name = 'general';

    //
    // Add getters and setters to each section.
    //
    var result = Object.create(null)
      , self = this;

    /**
     * Generate property descriptor.
     *
     * @param {Function} fn Method to call.
     * @param {Mixed} arg Additional default arguments, other than section and name.
     * @return {Object} Property description.
     */
    function property(fn, arg) {
      var base = [ section, name ];

      return {
        writable: false,
        enumerable: false,
        value: function () {
          if (arg) base.push(arg);

          return fn.apply(self, base.concat(
            Array.prototype.slice.apply(arguments)
          ));
        }
      };
    }

    Object.defineProperties(result, {
      get: property(self.get),
      set: property(self.set),
      add: property(self.add),
      comment: property(self.comment, 'pre'),
      and: {
        writable: false,
        enumerable: false,
        value: self
      }
    });

    //
    // Also add proxies for each key in the section.
    //
    Configuration.allowed[section].forEach(function addProxies(key) {
      var fn = Configuration.functionalize(key);

      if (Object.prototype.hasOwnProperty.call(result, fn)) return;
      Object.defineProperty(result, fn, property(self.set, key));
    }, this);

    return result;
  };
});

//
// Expose the Config
//
module.exports = Configuration;


<|start_filename|>test/config.sync.js<|end_filename|>
'use strict';

var request = require('request')
  , config = require('../lib/config')
  , version = process.env.VERSION || '1.5';

/**
 * Do an 'any' check against each scope of the config.
 *
 * @param {String} value Option to check.
 * @returns {Boolean}
 * @api private
 */
function check(value) {
  return Object.keys(config).reduce(function check(memo, key) {
    if (memo) return memo;
    return !!~config[key].indexOf(value);
  }, false);
}

/**
 * Report missing config value.
 *
 * @param {String} scope Scope of option.
 * @param {String} key Config option.
 * @api private
 */
function missing(scope, key) {
  if (check(key)) return;
  console.log('Missing %s option: %s', scope, key);
}

request([
  'http://www.haproxy.org/git?p=haproxy-',
  version,
  '.git;',
  'a=blob_plain;',
  'f=doc/configuration.txt;',
  'hb=HEAD'
].join(''), function conf(error, response, body) {
  if (error) throw error;

  console.log('Finished fetching configuration docs', body.length);

  //
  // Check of global parameters.
  //
  body.slice(
    body.indexOf('3. Global parameters'),
    body.indexOf('3.1. Process management and security')
  ).split('\n').forEach(function each(line) {
    if (!~line.indexOf('   - ')) return;
    missing('global', line.slice(5));
  });

  //
  // Check proxy configuration parameters.
  //
  body.slice(
    body.indexOf('4.1. Proxy keywords matrix'),
    body.indexOf('4.2. Alphabetically sorted keywords reference')
  ).split('\n').forEach(function each(line) {
    if (!~line.indexOf('     -    ')
      || !~line.indexOf('     X    ')
      || ~line.indexOf('deprecated')) return;

    //
    // Special case with inversion asterix.
    //
    if (~line.indexOf('(*)')) {
      return missing('proxy', line.slice(0, line.indexOf('(*)  X')).trim());
    }

    var optNeg = line.indexOf('     -')
      , optPos = line.indexOf('     X');

    missing('proxy', line.slice(0, optNeg < optPos ? optNeg : optPos).trim());
  });
});

<|start_filename|>lib/composer.js<|end_filename|>
'use strict';

/**
 * Compose different types of configurations from the given
 * `Configuration` instance.
 *
 * @constructor
 * @param {Configuration} config [description]
 * @api private
 */
function Composer(config) {
  if (!(this instanceof Composer)) return new Composer(config);

  this.config = config;
}

/**
 * Automatic JSON conversion when `JSON.stringify(composer)` is used.
 *
 * @returns {Object} A plain JavaScript object.
 * @api private
 */
Composer.prototype.toJSON = function toJSON() {
  return this.config.data;
};

/**
 * Create human readable stringified JSON representation of our config.
 *
 * @return {String} The configuration.
 * @api private
 */
Composer.prototype.json = function json() {
  return JSON.stringify(this, null, 2);
};
/**
 * Composer function for cfg according HAProxy and adds comments.
 *
 * @returns {String} A HAProxy compatible configuration file
 * @api private
 */
Composer.prototype.cfg = function cfg() {
  var data = this.config.data;

  return Object.keys(data).reduce(function addSections(result, key) {
    var current = data[key];

    return result + Object.keys(current).reduce(function addNames(named, name) {
      var sub = current[name]
        , comm = sub.commentary;

      // Output section and main comments.
      if (comm && comm.pre) result += '# '+ comm.pre +'\n';
      named += key + (name !== 'general' ? ' ' + name + '\n' : '\n');

      // Output section keys and values.
      return named += Object.keys(sub).reduce(function addKeys(section, key) {
        if (key === 'commentary') return section;

        // Check if the key has multiple values stored as array, otherwise
        // just add the key and value and comment (if required).
        if (Array.isArray(sub[key])) {
          var length = sub[key].length;

          for (var i = 0; i < length; i++) {
            section += '    '+ key + (sub[key][i].length ? ' '+ sub[key][i] : '');
            section += (i === 1 && comm && comm[key] ? ' # ' + comm[key] + '\n' : '\n');
          }

          return section;
        } else {
          return section += '    '+ key + (sub[key].length ? ' '+ sub[key] : '')
            + (comm && comm[key] ? ' # ' + comm[key] + '\n' : '\n');
        }
      }, '') + '\n';
    }, '');
  }, '');
};

//
// Expose the configuration composer.
//
module.exports = Composer;


<|start_filename|>test/haproxy.test.js<|end_filename|>
describe('haproxy', function () {
  'use strict';

  var request = require('request')
    , hamock = require('./hamock')
    , HAProxy = require('../')
    , chai = require('chai')
    , sinon = require('sinon')
    , expect = chai.expect;

  //
  // The location of the fixtures directory.
  //
  var path = require('path')
    , pidFile = path.resolve(__dirname, 'haproxy.pid')
    , fixtures = require('./fixtures')
    , fixturesPath = path.resolve(__dirname, 'fixtures')
    , orchestrator = fixturesPath + '/orchestrator.cfg'
    , sock = '/tmp/haproxy.sock'
    , timeout = 5000
    , servers
    , server;

  chai.use(require('sinon-chai'));
  chai.config.includeStack = true;

  //
  // Create a mockup server that responds with identical output as the HAProxy.
  //
  before(function before(done) {
    var online = 0;

    server = hamock.createServer({ socket: '/tmp/fixture.sock' }).listen(function () {
      if (++online === 2) done();
    });

    servers = [8083, 8084].reduce(function (set, port) {
      var app = require('http').createServer(function (req, res) {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/html');

        var interval = setInterval(function () {
          res.write('server\n');
        }, 100);

        setTimeout(function () {
          clearInterval(interval);
          res.end('Hello from port: '+port);
        }, timeout);
      });

      set['srv'+ port] = app;

      app.listen(port, function () {
        if (++online === 2) done();
      });

      return set;
    }, {});
  });

  after(function after() {
    Object.keys(servers).forEach(function close(id) {
      servers[id].close();
    });

    server.close();
  });

  it('is exported as a function', function () {
    expect(HAProxy).to.be.a('function');

    var proxy = new HAProxy({ socket: '/tmp/fixture.sock', config: '/foo', which: '/fake/path/haproxy' });

    expect(proxy).to.be.instanceof(require('events').EventEmitter);
    expect(proxy).to.be.instanceof(HAProxy);
  });

  it('accepts a single object as argument', function () {
    var proxy = new HAProxy({ socket: '/tmp/fixture.sock', config: '/foo', which: '/fake/path/haproxy' });

    expect(proxy.socket).to.equal('/tmp/fixture.sock');
    expect(proxy.cfg).to.equal('/foo');
  });

  it('accepts a socket first argument', function () {
    var proxy = new HAProxy('/foo.sock', {which: '/fake/path/haproxy'});

    expect(proxy.socket).to.equal('/foo.sock');
  });

  it('emits haproxy:down when it cannot connect to the given socket', function (done) {
    var proxy = new HAProxy('/tmp/wtf/socket/lol/random/path/should/be/ok/now.sock', {which: '/fake/path/haproxy'});

    proxy.on('haproxy:down', function down(err) {
      expect(err).to.be.instanceof(Error);

      done();
    });

    proxy.clear(function clear(err) {
      expect(err).to.be.instanceof(Error);
    });
  });

  describe('#load', function () {
    it('has #read as alias', function () {
      var proxy = new HAProxy('/foo.sock', {which: '/fake/path/haproxy' });

      expect(proxy.load).to.equal(proxy.read);
    });

    it('reads the given configuration file', function (done) {
      var proxy = new HAProxy('/foo.sock', { config: __dirname + '/fixtures/default.cfg', which: '/fake/path/haproxy' });

      proxy.load(__dirname + '/fixtures/comment.cfg', function (err, data) {
        if (err) return done(err);

        expect(data).to.include('different comment styles');
        expect(data).to.not.include('maximum time to wait for a connection attempt');
        done();
      });
    });

    it('reads the config from the options', function (done) {
      var proxy = new HAProxy('/foo.sock', { config: __dirname + '/fixtures/default.cfg', which: '/fake/path/haproxy' });

      proxy.load(function (err, data) {
        if (err) return done(err);

        expect(data).to.not.include('different comment styles');
        expect(data).to.include('maximum time to wait for a connection attempt');
        done();
      });
    });

    it('parses the configration', function (done) {
      var proxy = new HAProxy('/foo.sock', { config: __dirname + '/fixtures/default.cfg', which: '/fake/path/haproxy' });

      proxy.load(function (err, data) {
        if (err) return done(err);

        expect(data).to.equal(proxy.config.source);
        expect(proxy.config.get('global', 'general', 'pidfile')).to.equal('/tmp/haproxy.pid');

        done();
      });
    });
  });

  describe('#clear', function () {
    it('should clear the counters', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      proxy.clear(done);
    });

    it('should clear all the counters', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      proxy.clear(true, done);
    });
  });

  describe('#disable', function () {
    it('should disable the given backend', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.disable('realtime', 'node1', done);
    });

    it('should ignore errors when the backend is already disabled', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.disable('realtime', 'node1', done);
    });

    it('should error when a non existing backend is given', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.disable('realtimer', 'node1', function (err) {
        expect(err).to.be.instanceOf(Error);
        expect(err.message).to.equal('No such backend.');

        done();
      });
    });

    it('should error when a non existing server is given', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.disable('realtime', 'node2', function (err) {
        expect(err).to.be.instanceOf(Error);
        expect(err.message).to.equal('No such server.');

        done();
      });
    });
  });

  describe('#enable', function () {
    it('should enable a disabled backed', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.enable('realtime', 'node1', done);
    });

    it('should not give errors when the backend is already enabled', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.enable('realtime', 'node1', done);
    });

    it('should error when a non existing backend is given', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.enable('realtimer', 'node1', function (err) {
        expect(err).to.be.instanceOf(Error);
        expect(err.message).to.equal('No such backend.');

        done();
      });
    });

    it('should error when a non existing server is given', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.enable('realtime', 'node2', function (err) {
        expect(err).to.be.instanceOf(Error);
        expect(err.message).to.equal('No such server.');

        done();
      });
    });
  });

  describe('#pause', function () {
    it('should disable the frontend');
    it('should ignore errors when the frontend is already disabled');
    it('should error when a non existing frontend is given');
  });

  describe('#resume', function () {
    it('should resume the frontend');
    it('should ignore errors when the frontend is already active');
    it('should error when a non existing frontend is given');
  });

  describe('#errors', function () {
    it('return errors only for the given id');
    it('returns a list of errors');
    it('returns nothing when there are no errors');
  });

  describe('#weight', function () {
    it('returns the inital and current weight', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.weight('foo', 'bar', function (err, data) {
        expect(err).to.not.be.instanceOf(Error);

        expect(data.initial).to.equal(1);
        expect(data.current).to.equal(5);

        done();
      });
    });

    it('returns an error when the given backend does not exist');

    it('returns an error when the given server does not exist');

    it('should set the weight when a weight argument is supplied');

    it('should return an error when the set weight is to high');
  });

  describe('#maxconn', function () {
    it('sets the max connections for the frontend');
    it('sets the max connections globally');
    it('returns an error if the frontend is unknown');
  });

  describe('#ratelimit', function () {
    it('should rate limit the connections for a frontend');
    it('should rate limit the connections globally');
  });

  describe('#compression', function () {
    it('should rate limit the compression for a frontend');
    it('should rate limit the compression globally');
  });

  describe('#info', function () {
    it('returns an object with the HAProxy info', function (done) {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });

      proxy.info(function (err, data) {
        expect(err).to.not.be.instanceOf(Error);
        expect(data).to.be.a('object');

        expect(data.Tasks).to.equal(33);
        expect(data.PipesFree).to.equal(0);
        expect(data.Uptime).to.equal('0d 0h08m10s');

        done();
      });
    });
  });

  describe('#parse', function () {
    it('calls func parameter with true boolean result if buffer is falsey', function () {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      var funcSpy = sinon.spy();
      proxy.parse('csv', null, funcSpy);
      expect(funcSpy).to.be.calledWith(undefined, true);
    });
    it('parses output from haproxy socket using csv parser and calls func parameter with result', function () {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      var funcSpy = sinon.spy();
      proxy.parse('csv', fixtures['show stat'].toString(), funcSpy);
      expect(funcSpy).to.be.calledWith(undefined, fixtures['show stat parsed'].csv);
    });
    it('parses output from haproxy socket using string parser and calls func parameter with result', function () {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      var funcSpy = sinon.spy();
      proxy.parse('string', fixtures['show stat'].toString(), funcSpy);
      expect(funcSpy).to.be.calledWith(undefined, fixtures['show stat parsed'].string);
    });
    it('parses output from haproxy socket using array parser and calls func parameter with result', function () {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      var funcSpy = sinon.spy();
      proxy.parse('array', fixtures['show stat'].toString(), funcSpy);
      expect(funcSpy).to.be.calledWith(undefined, fixtures['show stat parsed'].array);
    });
    it('parses output from haproxy socket using a custom function parser and calls func parameter with result', function () {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      var funcSpy = sinon.spy();
      var parser = function (buffer) {
        return buffer.substring(0,5);
      };
      proxy.parse(parser, fixtures['show stat'].toString(), funcSpy);
      expect(funcSpy).to.be.calledWith(undefined, fixtures['show stat parsed'].func);
    });
    it('parses output from haproxy socket using object parser and calls func parameter with result if parser argument is unrecognized', function () {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      var funcSpy = sinon.spy();
      proxy.parse('unknown_parser', fixtures['show stat'].toString(), funcSpy);
      expect(funcSpy).to.be.calledWith(undefined, fixtures['show stat parsed'].obj);
    });
    it('returns error and undefined if haproxy socket response is a single line whitespace string', function () {
      var proxy = new HAProxy({ socket: '/tmp/fixture.sock', which: '/fake/path/haproxy' });
      var funcSpy = sinon.spy();
      var err = new Error(' ');
      err.command = 'blah';
      proxy.parse('unknown_parser', ' ', funcSpy, 'blah');
      expect(funcSpy).to.be.calledWith(err, undefined);
    });
  });
});


<|start_filename|>test/orchestrator.test.js<|end_filename|>
describe('haproxy:orchestrator', function () {
  'use strict';

  var request = require('request')
    , HAProxy = require('../')
    , chai = require('chai')
    , expect = chai.expect;

  //
  // The location of the fixtures directory.
  //
  var path = require('path')
    , pidFile = path.resolve(__dirname, 'haproxy.pid')
    , fixtures = path.resolve(__dirname, 'fixtures')
    , orchestrator = fixtures + '/orchestrator.cfg'
    , sock = '/tmp/haproxy.sock'
    , timeout = 5000
    , servers;

  chai.config.includeStack = true;

  //
  // Make sure that every process is dead before we start so we don't get random
  // failures because we still have a haproxy running locally for testing
  // purposes.
  //
  // In addition to that
  //
  before(function (done) {
    var haproxy = new HAProxy();

    //
    // Attempt to clean up all established HAProxies that are started
    //
    haproxy.stop(true, function () {
      var online = 0;

      servers = [8083, 8084].reduce(function (set, port) {
        var app = require('http').createServer(function (req, res) {
          res.statusCode = 200;
          res.setHeader('Content-Type', 'text/html');

          var interval = setInterval(function () {
            res.write('server\n');
          }, 100);

          setTimeout(function () {
            clearInterval(interval);
            res.end('Hello from port: '+port);
          }, timeout);
        });

        set['srv'+ port] = app;

        app.listen(port, function () {
          if (++online === 2) done();
        });

        return set;
      }, {});
    });
  });

  //
  // As we generated HTTP servers, we need to close them again.
  //
  after(function () {
    Object.keys(servers).forEach(function close(id) {
      servers[id].close();
    });
  });

  it('prefixes the haproxy command', function () {
    var haproxy = new HAProxy(sock, {
        config: orchestrator
      , pidFile: pidFile
      , prefix: 'sudo'
    });

    expect(haproxy.orchestrator.prefix).to.equal('sudo');
    expect(haproxy.orchestrator.which).to.contain('haproxy');
  });

  describe('#running', function () {
    it('should check if the current process is still running', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      haproxy.running(function (err, running) {
        if (err) return done(err);

        expect(running).to.equal(false);

        haproxy.start(function (err) {
          if (err) return done(err);

          haproxy.running(function (err, running) {

            if (err) return done(err);

            expect(running).to.equal(true);
            haproxy.stop(done);
          });
        });
      });
    });

    it('should autodetect proxies if none was started by the process', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      haproxy.start(function (err) {
        if (err) return done(err);

        var proxy = new HAProxy(sock, {
          config: orchestrator
        });

        proxy.running(function (err, running) {
          if (err) return done(err);

          expect(running).to.equal(true);
          haproxy.stop(done);
        });
      });
    });
  });

  describe('#start', function () {
    it('should set a pidfile if none exists', function (done) {
      var haproxy = new HAProxy(sock, {
        config: orchestrator
      });

      expect(haproxy.orchestrator.pidFile).to.equal('');
      haproxy.start(function (err) {
        // Technically this is throwing an error, since the pidfile cannot be
        // generated without sudo-rights, however we just ignore it for now.
        expect(haproxy.orchestrator.pidFile).to.equal('/var/run/haproxy.pid');

        done();
      });
    });

    it('should verify the given configuration', function (done) {
      var haproxy = new HAProxy(sock, {
          config: fixtures +'/broken.cfg'
        , pidFile: pidFile
      });

      haproxy.start(function starting(err) {
        expect(err).to.be.instanceof(Error);
        done();
      });
    });

    it('should run the application demonized', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      haproxy.start(function (err, res, cmd) {
        expect(cmd).to.include('haproxy');
        expect(cmd).to.include('-D');

        done(err);
      });
    });

    it('should store the new pid', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      expect(haproxy.orchestrator.pid).to.equal('');

      haproxy.start(function (err) {
        expect(haproxy.orchestrator.pid).to.not.equal('');
        done(err);
      });
    });
  });

  describe('#stop', function () {
    it('should stop all the running processes', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      var another = new HAProxy('/tmp/another.sock', {
          config: fixtures +'/another.cfg'
        , pidFile: '/tmp/another.pid'
      });

      //
      // start both servers, ensure they are both running, killall ensure that
      // they are all not running.
      //
      function running(value, fn) {
        haproxy.running(function (err, running) {
          if (err) return done(err);

          expect(running).to.equal(value);
          another.running(function (err, running) {
            if (err) return done(err);

            expect(running).to.equal(value);
            fn();
          });
        });
      }

      function stop(fn) {
        haproxy.stop(true, function (err) {
          if (err) return done(err);

          running(false, fn);
        });
      }

      haproxy.start(function (err) {
        if (err) return done(err);

        another.start(function (err) {
          if (err) return done(err);

          running(true, stop.bind(undefined, done));
        });
      });
    });

    it('should only stop the current running process', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      var another = new HAProxy('/tmp/another.sock', {
          config: fixtures +'/another.cfg'
        , pidFile: '/tmp/another.pid'
      });

      //
      // start both servers, ensure they are both running, killall ensure that
      // they are all not running.
      //
      function running(value, fn) {
        haproxy.running(function (err, running) {
          if (err) return done(err);

          expect(running).to.equal(value);
          another.running(function (err, running) {
            if (err) return done(err);

            expect(running).to.equal(value);
            fn();
          });
        });
      }

      function stop(fn) {
        haproxy.stop(function (err) {
          if (err) return done(err);

          haproxy.running(function (err, running) {
            if (err) return done(err);

            expect(running).to.equal(false);

            another.running(function (err, running) {
              if (err) return done(err);

              expect(running).to.equal(true);
              another.stop(fn);
            });
          });
        });
      }

      haproxy.start(function (err) {
        if (err) return done(err);

        another.start(function (err) {
          if (err) return done(err);

          running(true, stop.bind(undefined, done));
        });
      });
    });
  });

  describe('#softstop', function () {
    this.timeout(timeout + 500);

    it('killing me softly (with his song)', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      function finish() {
        if (++finish.done === 2) done();
      }
      finish.done = 0;

      //
      // The request will take at least 5 seconds to complete
      //
      haproxy.start(function (err) {
        if (err) return done(err);

        request('http://localhost:8080/foo/', function (err, res, body) {
          if (err) return done(err);

          expect(body).to.include('server');
          expect(body).to.include('ello from port');

          finish();
        });

        var start = Date.now();
        setTimeout(function () {
          haproxy.softstop(function (err) {
            if (err) return done(err);

            expect(Date.now() - start).to.be.above(timeout);
            expect(haproxy.orchestrator.pid).to.equal('');
            finish();
          });
        }, 100);
      });
    });
  });

  describe('#reload', function (done) {
    this.timeout(timeout + 500);

    it('should gracefully reload the server', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      function finish() {
        if (++finish.done === 2) done();
      }
      finish.done = 0;

      //
      // The request will take at least 5 seconds to complete
      //
      haproxy.start(function (err) {
        if (err) return done(err);

        request('http://localhost:8080/foo/', function (err, res, body) {
          if (err) return done(err);

          expect(body).to.include('server');
          expect(body).to.include('ello from port');

          finish();
        });

        var start = Date.now();
        setTimeout(function () {
          haproxy.reload(function (err) {
            if (err) return done(err);

            expect(Date.now() - start).to.be.below(timeout);
            finish();
          });
        }, 100);
      });
    });

    it('should reload the server instantly', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      function finish() {
        if (++finish.done === 2) done();
      }
      finish.done = 0;

      //
      // The request will take at least 5 seconds to complete
      //
      haproxy.start(function (err) {
        if (err) return done(err);

        request('http://localhost:8080/foo/', function (err, res) {
          if (err) expect(err.message).to.include('hang up');
          finish();
        });

        var start = Date.now();
        setTimeout(function () {
          haproxy.reload(true, function (err) {
            if (err) return done(err);

            expect(Date.now() - start).to.be.below(timeout);
            finish();
          });
        }, 100);
      });
    });

    it('set a new pid', function (done) {
      var haproxy = new HAProxy(sock, {
          config: orchestrator
        , pidFile: pidFile
      });

      haproxy.start(function (err) {
        if (err) return done(err);

        var pid = haproxy.orchestrator.pid;

        haproxy.reload(function (err) {
          if (err) return done(err);

          expect(haproxy.orchestrator.pid).to.not.equal(pid);
          haproxy.stop(done);
        });
      });
    });
  });

  describe('#verify', function () {
    it('should verify the correctness of the configuration', function (done) {
      var broken = new HAProxy(sock, {
        config: orchestrator
      });

      broken.verify(function (err, okay) {
        expect(okay).to.equal(true);
        done(err);
      });
    });

    it('should verify the broken configuration', function (done) {
      var broken = new HAProxy(sock, {
        config: fixtures +'/broken.cfg'
      });

      broken.verify(function (err, okay) {
        expect(okay).to.equal(false);
        done(err);
      });
    });
  });

  afterEach(function (done) {
    var haproxy = new HAProxy();

    //
    // Attempt to clean up all established HAProxies that are started
    //
    haproxy.stop(true, function () {
      done();
    });
  });
});


<|start_filename|>test/hamock.js<|end_filename|>
'use strict';

var net = require('net')
  , fs = require('fs');

/**
 * Create new HA proxy mock server.
 *
 * @param {Object} options
 * @api public
 */
exports.createServer = function createServer(options) {
  return new Hamock(options || {});
};

/**
 * A HAProxy unix-socket mockup server.
 *
 * @constructor
 * @param {Object} options
 * @api private
 */
function Hamock(options) {
  this.server = options.server || net.createServer({ allowHalfOpen: true });
  this.server.on('connection', this.handle.bind(this));
  this.commands = options.commands || require('./fixtures');
  this.socket = options.socket || '/tmp/haproxy.sock';
  this.sockets = [];
}

/**
 * Handles requests.
 *
 * @param {Socket} socket TCP socket of the connection.
 * @api private
 */
Hamock.prototype.handle = function handle(socket) {
  this.sockets.push(socket);

  var command = '';
  socket.setEncoding('utf-8');

  socket.on('data', function data(buffer) {
    command += buffer;
  }).on('end', function end() {
    var handler = command.trim()
      , data = this.commands[handler];

    socket.end(data || 'Unkown command: "'+ handler +'"');
  }.bind(this));

  socket.once('close', function close() {
    this.sockets.splice(this.sockets.indexOf(socket), 1);
  }.bind(this));
};

/**
 * Start listening on a something. Accepts what ever a plain net.Server accepts.
 *
 * @param {String} path Location of the socket;
 * @param {Function} fn Callback function
 * @api public
 */
Hamock.prototype.listen = function listen(path, fn) {
  if ('function' === typeof path) {
    fn = path;
    path = null;
  }

  if (path) this.socket = path;

  //
  // Clean up the existing socket so we can connect.
  //
  if (fs.existsSync(this.socket)) fs.unlinkSync(this.socket);

  this.server.listen(this.socket, fn);
  return this;
};

/**
 * Closes the server and kills all the connections.
 *
 * @param {Function} fn Callback when everthing is cleaned.
 * @api public
 */
Hamock.prototype.close = function close(fn) {
  this.sockets.forEach(function each(socket) {
    socket.end();

    if (socket.unref) socket.unref();
  });

  this.server.close(function closed() {
    if (fs.existsSync(this.socket)) fs.unlinkSync(this.socket);
    if (fn) fn.apply(fn, arguments);
  }.bind(this));

  return this;
};


<|start_filename|>test/fixtures/showstat-parsed.js<|end_filename|>
// showinfo.out that's been run through the parser

module.exports = {
  func: '# pxn',
  string: '# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,\npublic,FRONTEND,,,27,81,2000,21994,33421405,32806329,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,195,0,538,,,,21967,0,0,0,0,0,,194,540,21992,,,0,0,0,0,\nrealtime,node1,0,0,25,77,32768,21992,33421405,32806329,,0,,0,0,0,0,no check,1,1,0,,,,,,1,3,1,,21992,,2,194,,540,,,,21991,0,0,0,0,0,0,,,,0,0,,,,,\nrealtime,BACKEND,0,0,25,77,200,21992,33421405,32806329,0,0,,0,0,0,0,UP,1,1,0,,0,648,0,,1,3,0,,21992,,1,194,,540,,,,21967,0,0,0,0,0,,,,,0,0,0,0,0,0,\n\n',
  array: [
    '# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,',
    'public,FRONTEND,,,27,81,2000,21994,33421405,32806329,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,195,0,538,,,,21967,0,0,0,0,0,,194,540,21992,,,0,0,0,0,',
    'realtime,node1,0,0,25,77,32768,21992,33421405,32806329,,0,,0,0,0,0,no check,1,1,0,,,,,,1,3,1,,21992,,2,194,,540,,,,21991,0,0,0,0,0,0,,,,0,0,,,,,',
    'realtime,BACKEND,0,0,25,77,200,21992,33421405,32806329,0,0,,0,0,0,0,UP,1,1,0,,0,648,0,,1,3,0,,21992,,1,194,,540,,,,21967,0,0,0,0,0,,,,,0,0,0,0,0,0,',
    '',
    ''
  ],
  obj: {},
  csv: [{
    act: "",
    bck: "",
    bin: "33421405",
    bout: "32806329",
    check_code: "",
    check_duration: "",
    check_status: "",
    chkdown: "",
    chkfail: "",
    cli_abrt: "",
    comp_byp: "0",
    comp_in: "0",
    comp_out: "0",
    comp_rsp: "0",
    downtime: "",
    dreq: "0",
    dresp: "0",
    econ: "",
    ereq: "0",
    eresp: "",
    hanafail: "",
    hrsp_1xx: "21967",
    hrsp_2xx: "0",
    hrsp_3xx: "0",
    hrsp_4xx: "0",
    hrsp_5xx: "0",
    hrsp_other: "0",
    iid: "2",
    lastchg: "",
    lbtot: "",
    pid: "1",
    pxname: "public",
    qcur: "",
    qlimit: "",
    qmax: "",
    rate: "195",
    rate_lim: "0",
    rate_max: "538",
    req_rate: "194",
    req_rate_max: "540",
    req_tot: "21992",
    scur: "27",
    sid: "0",
    slim: "2000",
    smax: "81",
    srv_abrt: "",
    status: "OPEN",
    stot: "21994",
    svname: "FRONTEND",
    throttle: "",
    tracked: "",
    type: "0",
    weight: "",
    wredis: "",
    wretr: ""
  }, {
    act: "1",
    bck: "0",
    bin: "33421405",
    bout: "32806329",
    check_code: "",
    check_duration: "",
    check_status: "",
    chkdown: "",
    chkfail: "",
    cli_abrt: "0",
    comp_byp: "",
    comp_in: "",
    comp_out: "",
    comp_rsp: "",
    downtime: "",
    dreq: "",
    dresp: "0",
    econ: "0",
    ereq: "",
    eresp: "0",
    hanafail: "0",
    hrsp_1xx: "21991",
    hrsp_2xx: "0",
    hrsp_3xx: "0",
    hrsp_4xx: "0",
    hrsp_5xx: "0",
    hrsp_other: "0",
    iid: "3",
    lastchg: "",
    lbtot: "21992",
    pid: "1",
    pxname: "realtime",
    qcur: "0",
    qlimit: "",
    qmax: "0",
    rate: "194",
    rate_lim: "",
    rate_max: "540",
    req_rate: "",
    req_rate_max: "",
    req_tot: "",
    scur: "25",
    sid: "1",
    slim: "32768",
    smax: "77",
    srv_abrt: "0",
    status: "no check",
    stot: "21992",
    svname: "node1",
    throttle: "",
    tracked: "",
    type: "2",
    weight: "1",
    wredis: "0",
    wretr: "0"
  }, {
    act: "1",
    bck: "0",
    bin: "33421405",
    bout: "32806329",
    check_code: "",
    check_duration: "",
    check_status: "",
    chkdown: "0",
    chkfail: "",
    cli_abrt: "0",
    comp_byp: "0",
    comp_in: "0",
    comp_out: "0",
    comp_rsp: "0",
    downtime: "0",
    dreq: "0",
    dresp: "0",
    econ: "0",
    ereq: "",
    eresp: "0",
    hanafail: "",
    hrsp_1xx: "21967",
    hrsp_2xx: "0",
    hrsp_3xx: "0",
    hrsp_4xx: "0",
    hrsp_5xx: "0",
    hrsp_other: "0",
    iid: "3",
    lastchg: "648",
    lbtot: "21992",
    pid: "1",
    pxname: "realtime",
    qcur: "0",
    qlimit: "",
    qmax: "0",
    rate: "194",
    rate_lim: "",
    rate_max: "540",
    req_rate: "",
    req_rate_max: "",
    req_tot: "",
    scur: "25",
    sid: "0",
    slim: "200",
    smax: "77",
    srv_abrt: "0",
    status: "UP",
    stot: "21992",
    svname: "BACKEND",
    throttle: "",
    tracked: "",
    type: "1",
    weight: "1",
    wredis: "0",
    wretr: "0"
  }, {
    act: undefined,
    bck: undefined,
    bin: undefined,
    bout: undefined,
    check_code: undefined,
    check_duration: undefined,
    check_status: undefined,
    chkdown: undefined,
    chkfail: undefined,
    cli_abrt: undefined,
    comp_byp: undefined,
    comp_in: undefined,
    comp_out: undefined,
    comp_rsp: undefined,
    downtime: undefined,
    dreq: undefined,
    dresp: undefined,
    econ: undefined,
    ereq: undefined,
    eresp: undefined,
    hanafail: undefined,
    hrsp_1xx: undefined,
    hrsp_2xx: undefined,
    hrsp_3xx: undefined,
    hrsp_4xx: undefined,
    hrsp_5xx: undefined,
    hrsp_other: undefined,
    iid: undefined,
    lastchg: undefined,
    lbtot: undefined,
    pid: undefined,
    pxname: "",
    qcur: undefined,
    qlimit: undefined,
    qmax: undefined,
    rate: undefined,
    rate_lim: undefined,
    rate_max: undefined,
    req_rate: undefined,
    req_rate_max: undefined,
    req_tot: undefined,
    scur: undefined,
    sid: undefined,
    slim: undefined,
    smax: undefined,
    srv_abrt: undefined,
    status: undefined,
    stot: undefined,
    svname: undefined,
    throttle: undefined,
    tracked: undefined,
    type: undefined,
    weight: undefined,
    wredis: undefined,
    wretr: undefined
  }]
};