Runnable/docker-mock

View on GitHub
lib/models/container.js

Summary

Maintainability
B
6 hrs
Test Coverage
'use strict'

var assign = require('101/assign')
var EventEmitter = require('events').EventEmitter
var mapKeys = require('object-loops/map-keys')
var NotModifiedError = require('./base-store').NotModifiedError
var Promise = require('bluebird')
var stream = require('stream')
var util = require('util')
var utils = require('../utils')

var sampleStats = require('./stats-sample.json')

module.exports = Container

var streams = Object.create(null)

util.inherits(StatsStream, stream.Readable)
function StatsStream (container, opt) {
  stream.Readable.call(this, opt)
  this.container = container
}

StatsStream.prototype._read = function () {
  if (this.container.State.Running) {
    this.push(sampleStats)
  } else {
    this.container.once('event', function (event) {
      if (event === 'start' || event === 'restart') {
        this.push(sampleStats)
      }
    }.bind(this))
  }
}

function Container (opts) {
  if (!opts) { opts = {} }
  var Id = utils.randomId()
  var data = {
    Id: Id,
    Memory: 0,
    MemorySwap: 0,
    Image: null,
    Config: {
      AttachStderr: true,
      AttachStdin: false,
      AttachStdout: true,
      Cmd: [],
      Env: [],
      ExposedPorts: {},
      Hostname: '',
      Image: null,
      // FIXME: this is a hack
      Labels: opts.Labels || {},
      OpenStdin: false,
      PortSpecs: null,
      StdinOnce: false,
      Tty: false,
      User: '',
      Volumes: {},
      WorkingDir: ''
    },
    HostConfig: {
      Dns: null,
      VolumesFrom: ''
    },
    Volumes: {},
    State: {
      Running: false,
      Pid: -1
    },
    NetworkSettings: {
      Bridge: '',
      Gateway: '',
      IPAddress: '',
      IPPrefixLen: 0,
      MacAddress: '',
      Ports: null
    }
  }
  delete opts.Labels
  var capitalizedOpts = mapKeys(opts, utils.capitalize)
  if (capitalizedOpts.Name) {
    capitalizedOpts.Name = '/' + capitalizedOpts.Name
  }
  assign(this, data, capitalizedOpts)

  streams[Id] = new StatsStream(this, {
    objectMode: true,
    highWaterMark: 1
  })
}
util.inherits(Container, EventEmitter)

Container.prototype.start = function (wasRestart) {
  return Promise.resolve()
    .bind(this)
    .then(function () {
      if (this.State.Running) {
        throw new NotModifiedError('Container already running')
      }

      this.State = {
        // TODO: more data!
        Running: true,
        Pid: utils.newPid()
      }
      this.NetworkSettings = {
        Bridge: 'docker0',
        Gateway: '172.17.42.1',
        IPAddress: '172.17.0.' + utils.newPort(),
        IPPrefixLen: 16,
        MacAddress: '02:42:ac:11:00:05',
        Ports: {
          '80/tcp': [{ HostPort: utils.newPort() }],
          '443/tcp': [{ HostPort: utils.newPort() }],
          '15000/tcp': [{ HostPort: utils.newPort() }]
        }
      }
      return this
    })
    .then(function (container) {
      this.emit('event', 'start', container)
      if (wasRestart) {
        this.emit('event', 'restart', container)
      }

      return container
    })
}

Container.prototype.onDelete = function () {
  this.removeAllListeners('event')
  streams[this.Id].push(null)
}

Container.prototype.stop = function (ignoreStopped, killSignal) {
  var exitCode = (killSignal === 'SIGKILL') ? 1 : 0
  return Promise.resolve()
    .bind(this)
    .then(function () {
      if (this.State.Running === false && !ignoreStopped) {
        throw new NotModifiedError('Container already stopped')
      }

      this.State = {
        // TODO: more data!
        ExitCode: exitCode,
        Running: false,
        Pid: 0
      }
      this.NetworkSettings = {
        Bridge: '',
        Gateway: '',
        IPAddress: '',
        IPPrefixLen: 0,
        MacAddress: '',
        Ports: null
      }

      return this
    })
    .then(function (container) {
      this.emit('event', 'die', container)
      if (!ignoreStopped) {
        // stop doesn't ignore stopped
        this.emit('event', 'stop', container)
      } else if (ignoreStopped === 'kill') {
        this.emit('event', 'kill', container)
      }

      // nothing special for restart
      return container
    })
}

Container.prototype.getStats = function (req, res) {
  // The default value is stream=1
  if (req.query.stream === '0') {
    res.send(sampleStats)
  } else {
    res.writeHead(200, {
      'content-type': 'text/plain; charset=utf-8',
      'transfer-encoding': 'chunked'
    })

    var stream = streams[this.Id]

    stream.on('readable', function () {
      function periodicRead () {
        var chunk = stream.read()
        if (chunk) {
          res.write(JSON.stringify(chunk) + '\n')
          setTimeout(function () {
            periodicRead()
          }, 1000)
        }
      }

      periodicRead()
    })

    stream.on('end', function () {
      res.end()
    })
    stream.on('error', function () {
      res.status(500).end()
    })
  }
}