Published on

TURN Server monitor Process 구현

1. TURN Server monitor Process의 역할

  • TURN credential id, pw가 외부로 노출될 경우를 대비해, turnadmin을 직접 호출하여 TURN credential id, pw를 주기적으로 갱신해준다.

  • REST서버를 내부적으로 실행하여 client의 TURN credential id, pw 요청을 처리한다. (Get turncredential API 제공)

  • Zookeeper에 TURN서버 정보와 물리적 서버의 트래픽 정보를 업데이트한다. (분산처리)

  • TURN 서버를 child process로 실행시키고, TURN 서버가 죽는 경우 재실행 시켜준다. (자신이 죽는 경우는 pm2에 의해서 재실행된다.)

2. TURN credential id, pw 관리 구현

  • turadmin을 통한 TURN credential id, pw 의 주기적인 갱신 및 Get turncredential API 제공
function addUserByTurnAdmin(userId, password, callback) {
  exec(
    'sudo turnadmin -a -b ' +
      constants.TURN_DB_PATH +
      ' -u ' +
      userId +
      ' -r ' +
      constants.TURN_REALM +
      ' -p ' +
      password,
    function (error, stdout, stderr) {
      if (error !== null) {
        logger.errorLog('exec turnadmin add/update user error: ' + error);
        callback(-1);
      } else {
        logger.debugLog(
          'turnadmin add/update id : ' +
            userId +
            ', realm : ' +
            constants.TURN_REALM +
            ', password : ' +
            password +
            ', db_path : ' +
            constants.TURN_DB_PATH
        );
        callback(0);
      }
    }
  );
}

function removeUserByTurnAdmin(userId, callback) {
  exec(
    'sudo turnadmin -d -u ' + userId + ' -r ' + constants.TURN_REALM,
    function (error, stdout, stderr) {
      if (error !== null) {
        logger.errorLog('exec turnadmin add/update user error: ' + error);
        callback(-1);
      } else {
        logger.debugLog('turnadmin delete id : ' + userId + ', realm : ' + constants.TURN_REALM);
        callback(0);
      }
    }
  );
}

expressApp.route('/turncredential').get(function (req, res, next) {
  var userid = '',
    password = '',
    expired_time = 0,
    credentialJSON = turncredential.getActiveCredential();

  if (credentialJSON) {
    userid = credentialJSON.userid;
    password = credentialJSON.password;
    expired_time = credentialJSON.expired_time;
  }
  res.writeHead(200, { 'content-type': 'text/plain' });
  res.write(
    '{ "type" : "result", "code" : "ok", "userid" : ' +
      '"' +
      userid +
      '"' +
      ', "password" : ' +
      '"' +
      password +
      '"' +
      ', "expired_time" : ' +
      expired_time +
      ' }'
  );
  res.end();
});

3. Zookeeper에 TURN서버 정보와 서버트래픽 정보 업데이트 구현

  • Signalling 서버에서는 Zookeeper를 통해 TURN서버 리스트를 불러오고, 이 중 여유 트래픽이 있는 서버를 라운드로빈 방식으로 Client에 할당한다.
/* 트래픽 계산 */
var checkTraffic = function (callback) {
  exec('cat /sys/class/net/bond0/statistics/rx_bytes', function (error, stdout, stderr) {
    var rxbyte = stdout;
    if (error !== null) {
      console.log('exec error: ' + error);
    }
    logger.debugLog('Get rxbyte : ' + rxbyte);
    callback(rxbyte);
  });
};

checkTraffic(function (rxbyte) {
  var freeTraffic;
  if (!prevRxByte) {
    rxBytePer10Sec = 0;
  } else {
    rxBytePer10Sec = rxbyte - prevRxByte;
  }
  prevRxByte = rxbyte;
  freeTraffic = ((constants.MAX_TRAFFIC_BPS - (rxBytePer10Sec * 8) / 10) / 1000000).toFixed(1); //calc freetraffic by Mbps
  sData =
    '{"freeTraffic":' +
    freeTraffic +
    ', "restAddr":' +
    '"' +
    constants.TURN_MONITOR_SERVER_IP +
    ':' +
    constants.TURN_MONITOR_SERVER_PORT +
    '"' +
    '}';

  /* ZooKeeper에 업데이트 */
  if (stat) {
    zclient.setData(sPath, new Buffer(sData), function (error, stat) {
      if (error) {
        logger.errorLog(' ZooKeeper sendSummary - ' + error.stack);
        return;
      }
      logger.debugLog(' ZooKeeper SendSumary - node (' + sPath + '), data (' + sData + ') set');
    });
  } else {
    zclient.create(
      sPath,
      new Buffer(sData),
      zooKeeper.CreateMode.EPHEMERAL,
      function (error, path) {
        if (error) {
          logger.debugLog(
            ' ZooKeeper SendSummary - failed to create node (' + path + '), due to (' + error + ')'
          );
          return;
        } else {
          logger.debugLog(
            ' ZooKeeper SendSummary - node(' +
              path +
              ') is successfully created. Data is set (' +
              sData +
              ')'
          );
        }
      }
    );
  }
});

4. TURN서버를 child process로 실행, TURN서버가 죽는 경우 재실행 구현

  • TURN 서버를 child process로 실행시키고, child process가 죽는 경우 3초후에 다시 실행시킨다.

  • monitor process 자신이 죽는 경우, Zookeeper에서 해당 TURN server정보를 빼고, child turn process도 죽인후에 shutdown된다. 이 후 monitor process는 pm2에 의해 다시 살아난다.

function start() {
  global.turnProcess = child_process.spawn('turnserver');

  global.turnProcess.stdout.on('data', function (data) {
    //logging.debugLog(data.toString());
  });

  global.turnProcess.stderr.on('data', function (data) {
    //logging.debugLog(data.toString());
  });

  global.turnProcess.on('exit', function (code) {
    logging.debugLog('child process exited with code ' + code);
    setTimeout(start, 3000);
  });
}

process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);

var gracefulShutdown = function () {
  logging.debugLog('gracefulShutdown !!');
  zookeeper.removeNode(function (code) {
    var credentialJSON = turncredential.getActiveCredential();
    var currentTime = new Date().getTime();
    if (
      credentialJSON &&
      credentialJSON.userid &&
      credentialJSON.password &&
      credentialJSON.expired_time > currentTime
    ) {
      var writeData =
        '{"userid" : ' +
        '"' +
        credentialJSON.userid +
        '"' +
        ', "password" : ' +
        '"' +
        credentialJSON.password +
        '"' +
        ', "expired_time" : ' +
        credentialJSON.expired_time +
        '}';
      fs.writeFile('./credential', writeData, function (error) {
        if (global.turnProcess) {
          global.turnProcess.kill();
        }
        process.exit();
      });
    } else {
      if (global.turnProcess) {
        global.turnProcess.kill();
      }
      process.exit();
    }
  });
};