Mwalker has uploaded a new change for review.
https://gerrit.wikimedia.org/r/95644
Change subject: WIP Node.JS Renderer
......................................................................
WIP Node.JS Renderer
Change-Id: I652de11abe0fd48a8a9d48572b85177cccb91560
---
A collectoid/README.md
A collectoid/collectoid.js
A collectoid/defaults.json
A collectoid/frontend.js
A collectoid/package.json
5 files changed, 309 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Collection
refs/changes/44/95644/1
diff --git a/collectoid/README.md b/collectoid/README.md
new file mode 100644
index 0000000..b5b1652
--- /dev/null
+++ b/collectoid/README.md
@@ -0,0 +1,2 @@
+Ubuntu Dependencies
+-- redis-server
\ No newline at end of file
diff --git a/collectoid/collectoid.js b/collectoid/collectoid.js
new file mode 100755
index 0000000..51a72d2
--- /dev/null
+++ b/collectoid/collectoid.js
@@ -0,0 +1,97 @@
+#!/usr/bin/env node
+
+/*
+ Startup file for the WMF renderer
+
+ There are three main components that this script will manage.
+ - The frontend is responsible for handling all HTTP bookcmd requests
+ - Spider clients are responsible for getting all content
+ - Render clients are responsible for rendering content
+
+ This script will cluster.fork() as many frontends as configured (default
+ is one) and then poll redis in the origin thread spawning clients as
+ required.
+*/
+
+var nconf = require('nconf');
+require('rconsole');
+var redis = require('redis');
+var sleep = require('sleep');
+
+var redisClient = null;
+
+/* --- Configuration Options & File ---------------------------------------- */
+nconf
+ .argv({
+ c: {
+ alias: 'config-file',
+ describe: 'Local configuration file',
+ default: '/etc/collectoid.json'
+ }
+ })
+ .file({file: nconf.get('config-file')})
+ .file({file: 'defaults.json'});
+
+/* --- Initial Logging ----------------------------------------------------- */
+console.set({
+ facility: 'local0',
+ title: 'collectoid'
+});
+
+/* --- Redis Connection ---------------------------------------------------- */
+function startRedis() {}
+console.debug(
+ 'Starting connection to redis on %s, port %s (using password: %s)',
+ nconf.get("redis:host"),
+ nconf.get("redis:port"),
+ nconf.get("redis:password") ? true : false
+);
+redisClient = redis.createClient(
+ nconf.get("redis:port"),
+ nconf.get("redis:host"),
+ {
+ enable_offline_queue: false,
+ retry_max_delay: nconf.get("redis:retry_max_delay"),
+ auth_pass: nconf.get("redis:password")
+ }
+);
+function redisConnectionClosed( err ) {
+ console.error(
+ 'Redis connection (now %s) had error: %s',
+ redisClient.connected ? 'connected': 'disconnected',
+ err
+ );
+}
+redisClient.on("error", redisConnectionClosed);
+redisClient.on("end", redisConnectionClosed);
+redisClient.on("ready", function() {
+ console.debug("Redis connection now ready");
+});
+
+/* --- Process management -------------------------------------------------- */
+var respawnThreads = true;
+
+function gracefulShutdown() {
+ respawnThreads = false;
+ console.info('Beginning graceful shutdown');
+ frontend.stopServer(process.exit);
+ //process.exit();
+}
+function immediateShutdown() {
+ respawnThreads = false;
+ console.info('Shutting down immediately');
+
+ process.exit();
+}
+
+process.on('SIGINT', gracefulShutdown);
+process.on('SIGTERM', gracefulShutdown);
+process.on('SIGHUP', immediateShutdown);
+
+/* --- Frontend thread startup --------------------------------------------- */
+var frontend = require('./frontend.js' );
+frontend.init(nconf, redisClient);
+frontend.startServer();
+
+/* --- Client management --------------------------------------------------- */
+//redisClient.pop();
diff --git a/collectoid/defaults.json b/collectoid/defaults.json
new file mode 100644
index 0000000..45ef2d7
--- /dev/null
+++ b/collectoid/defaults.json
@@ -0,0 +1,17 @@
+{
+ "redis": {
+ "host": "localhost",
+ "port": 6379,
+ "password": null,
+ "retry_max_delay": 60000,
+
+ "spider_queue_name": "spider_queue",
+ "render_queue_name": "render_queue",
+ "status_set_name": "job_status"
+ },
+ "frontend": {
+ "socket": null,
+ "port": 17080,
+ "address": null
+ }
+}
\ No newline at end of file
diff --git a/collectoid/frontend.js b/collectoid/frontend.js
new file mode 100644
index 0000000..d39c76a
--- /dev/null
+++ b/collectoid/frontend.js
@@ -0,0 +1,179 @@
+var http = require('http');
+var url = require('url');
+var crypto = require('crypto');
+
+var config = null;
+var server = null;
+var redisClient = null;
+
+exports.init = function(nconf, redis) {
+ config = nconf;
+ redisClient = redis;
+
+ server = http.createServer(handleRequest);
+};
+
+/**
+ * Starts the frontend server
+ * @public
+ */
+exports.startServer = function() {
+ var socket = config.get('frontend:socket');
+ var port = config.get('frontend:port');
+ var address = config.get('frontend:address');
+
+ if (socket) {
+ console.info('Frontend server reporting for duty on unix socket
"%s"', socket);
+ server.listen( socket );
+ } else if (port) {
+ console.info(
+ 'Frontend server reporting for duty on %s, port %s',
+ address ? address : 'all IP interfaces',
+ port
+ );
+ server.listen(port, address);
+ } else {
+ console.error('Frontend could not start, no server parameters
specified');
+ process.abort();
+ }
+};
+
+exports.stopServer = function(callbackFunc) {
+ server.close(callbackFunc);
+};
+
+function handleRequest(request, response) {
+ if (request.method !== "GET") {
+ response.writeHead(405, "Only GET requests are supported");
+ response.end();
+ } else {
+ var args = url.parse(request.url, true ).query;
+
+ try {
+ switch (args.bookcmd) {
+ case "render":
+ var retval = handleRender(args);
+ console.debug('handle render returned
%s', JSON.stringify(retval));
+ response.write(JSON.stringify(retval));
+ break;
+ case "render_status":
+
response.write(handleRenderStatus(args));
+ break;
+ case "download":
+ handleDownload(args, response);
+ break;
+ }
+ } catch(err) {
+ if (err instanceof FrontendError) {
+ console.error("Error whilst serving request:
%s", err.message);
+ response.writeHead(err.code || 500,
err.message);
+ } else {
+ console.trace('Unknown error: ' + err);
+ response.writeHead(500, "Unexpected server
error");
+ }
+ } finally {
+ response.end();
+ }
+ }
+}
+
+function handleRender(args) {
+ var collectionId = args.collection_id;
+ var metabook = args.metabook;
+ var metabookObj = {};
+ var writer = args.writer;
+ var language = args.language;
+ var forceRender = args.force_render || false;
+ var isCached = false;
+
+ if (!writer) {
+ throw new FrontendError('Writer not specified', 400);
+ }
+
+ if (!collectionId) {
+ // Create the SHA hash of this collection request
+ if (!metabook) {
+ throw new FrontendError('Metabook parameter not given',
400);
+ } else {
+ metabookObj = JSON.parse(metabook);
+ if (!metabookObj) {
+ throw new FrontendError('Metabook parameter was
not valid JSON', 400);
+ }
+ collectionId = createCollectionId(metabookObj);
+ }
+ }
+
+ if (!forceRender) {
+ // TODO: Check to see if we already have a copy of the rendered
content somewhere
+ }
+
+ if (!isCached || forceRender) {
+ // Shove a new job into redis
+ console.info('Adding job with id %s to redis', collectionId);
+ try {
+ redisClient.rpush(
+ config.get('redis:spider_queue_name'),
+ collectionId
+ );
+ redisClient.sadd(
+ config.get('redis:status_set_name'),
+ JSON.stringify({
+ id: collectionId,
+ metabook: metabookObj,
+ writer: writer,
+ language: language
+ })
+ )
+ } catch (err) {
+ console.error('Job insertion into redis failed for job
%s with error: %s', collectionId, err);
+ throw new FrontendError('Job insertion failed (redis
failure?)', 500);
+ }
+ }
+
+ return {
+ collection_id: collectionId,
+ writer: writer,
+ is_cached: isCached
+ };
+}
+
+function handleRenderStatus(args) {
+ var collectionId = args.collection_id;
+
+
+}
+
+function handleDownload(args, response) {
+
+}
+
+function createCollectionId(metabookObj) {
+ collectionId = crypto.createHash('sha1');
+ collectionId.update(writer);
+ collectionId.update(metabookObj.title);
+ collectionId.update(metabookObj.subtitle);
+
+ var updateChapter = function(items) {
+ items.forEach(function(item) {
+ if (item.type === 'chapter') {
+ collectionId.update(item.title);
+ updateChapter(item.items);
+ } else {
+ collectionId.update(item.url);
+ if (item.currentVersion) {
+ collectionId.update(item.latest);
+ } else {
+ collectionId.update(item.revision);
+ }
+ }
+ });
+ };
+ updateChapter(metabookObj.items);
+
+ return collectionId.digest('hex');
+}
+
+var FrontendError = function( message, code ) {
+ this.message = message;
+ this.code = code || 500;
+};
diff --git a/collectoid/package.json b/collectoid/package.json
new file mode 100644
index 0000000..15b7805
--- /dev/null
+++ b/collectoid/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "Collectoid",
+ "description": "Backend render server for the collection extension",
+ "version": "0.0.1",
+ "dependencies": {
+ "cluster": "*",
+ "crypto": "*",
+ "nconf": "*",
+ "rconsole": "*",
+ "sleep": "*",
+ "redis": "*",
+ "hiredis": "*"
+ }
+}
\ No newline at end of file
--
To view, visit https://gerrit.wikimedia.org/r/95644
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I652de11abe0fd48a8a9d48572b85177cccb91560
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Collection
Gerrit-Branch: master
Gerrit-Owner: Mwalker <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits