Skip to content

Commit

Permalink
Merge branch 'multi-server-update' into devel
Browse files Browse the repository at this point in the history
  • Loading branch information
Naomi Seyfer committed Dec 13, 2013
2 parents 6a9a797 + 7449577 commit f92cb7d
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 67 deletions.
55 changes: 42 additions & 13 deletions packages/application-configuration/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,23 @@ AppConfig.findGalaxy = _.once(function () {
var ultra = AppConfig.findGalaxy();

var subFuture = new Future();
if (ultra)
var subFutureJobs = new Future();
if (ultra) {
ultra.subscribe("oneApp", process.env.GALAXY_APP, subFuture.resolver());
var OneAppApps;
ultra.subscribe("oneJob", process.env.GALAXY_JOB, subFutureJobs.resolver());
}

var Apps;
var Jobs;
var Services;
var collectionFuture = new Future();

Meteor.startup(function () {
if (ultra) {
OneAppApps = new Meteor.Collection("apps", {
Apps = new Meteor.Collection("apps", {
connection: ultra
});
Jobs = new Meteor.Collection("jobs", {
connection: ultra
});
Services = new Meteor.Collection('services', {
Expand All @@ -36,9 +44,15 @@ Meteor.startup(function () {
// places.
AppConfig._getAppCollection = function () {
collectionFuture.wait();
return OneAppApps;
return Apps;
};

AppConfig._getJobsCollection = function () {
collectionFuture.wait();
return Jobs;
};


var staticAppConfig;

try {
Expand Down Expand Up @@ -72,25 +86,41 @@ AppConfig.getAppConfig = function () {
return staticAppConfig;
}
subFuture.wait();
var myApp = OneAppApps.findOne(process.env.GALAXY_APP);
if (myApp)
return myApp.config;
throw new Error("there is no app config for this app");
var myApp = Apps.findOne(process.env.GALAXY_APP);
if (!myApp) {
throw new Error("there is no app config for this app");
}
var config = myApp.config;
return config;
};

AppConfig.getStarForThisJob = function () {
if (ultra) {
subFutureJobs.wait();
var job = Jobs.findOne(process.env.GALAXY_JOB);
if (job) {
return job.star;
}
}
return null;
};

AppConfig.configurePackage = function (packageName, configure) {
var appConfig = AppConfig.getAppConfig(); // Will either be based in the env var,
// or wait for galaxy to connect.
var lastConfig =
(appConfig && appConfig.packages && appConfig.packages[packageName]) || {};
(appConfig && appConfig.packages &&
appConfig.packages[packageName]) || {};

// Always call the configure callback "soon" even if the initial configuration
// is empty (synchronously, though deferred would be OK).
// XXX make sure that all callers of configurePackage deal well with multiple
// callback invocations! eg, email does not
configure(lastConfig);
var configureIfDifferent = function (app) {
if (!EJSON.equals(app.config && app.config.packages && app.config.packages[packageName],
lastConfig)) {
if (!EJSON.equals(
app.config && app.config.packages && app.config.packages[packageName],
lastConfig)) {
lastConfig = app.config.packages[packageName];
configure(lastConfig);
}
Expand All @@ -104,7 +134,7 @@ AppConfig.configurePackage = function (packageName, configure) {
// there's a Meteor.startup() that produces the various collections, make
// sure it runs first before we continue.
collectionFuture.wait();
subHandle = OneAppApps.find(process.env.GALAXY_APP).observe({
subHandle = Apps.find(process.env.GALAXY_APP).observe({
added: configureIfDifferent,
changed: configureIfDifferent
});
Expand All @@ -119,7 +149,6 @@ AppConfig.configurePackage = function (packageName, configure) {
};
};


AppConfig.configureService = function (serviceName, configure) {
if (ultra) {
// there's a Meteor.startup() that produces the various collections, make
Expand Down
36 changes: 34 additions & 2 deletions packages/ctl-helper/ctl-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ _.extend(Ctl, {
var numServers = Ctl.getJobsByApp(
Ctl.myAppName(), {program: program, done: false}).count();
if (numServers === 0) {
Ctl.startServerlikeProgram(program, tags, admin);
return Ctl.startServerlikeProgram(program, tags, admin);
} else {
console.log(program, "already running.");
}
return null;
},

startServerlikeProgram: function (program, tags, admin) {
Expand All @@ -47,6 +48,7 @@ _.extend(Ctl, {

var proxyConfig;
var bindPathPrefix = "";
var jobId = null;
if (admin) {
bindPathPrefix = "/" + encodeURIComponent(Ctl.myAppName()).replace(/\./g, '_');
}
Expand All @@ -60,7 +62,7 @@ _.extend(Ctl, {
});

// XXX args? env?
Ctl.prettyCall(Ctl.findGalaxy(), 'run', [Ctl.myAppName(), program, {
jobId = Ctl.prettyCall(Ctl.findGalaxy(), 'run', [Ctl.myAppName(), program, {
exitPolicy: 'restart',
env: {
ROOT_URL: "https://" + appConfig.sitename + bindPathPrefix,
Expand All @@ -80,6 +82,7 @@ _.extend(Ctl, {
tags: tags
}]);
console.log("Started", program);
return jobId;
},

findCommand: function (name) {
Expand Down Expand Up @@ -130,6 +133,35 @@ _.extend(Ctl, {
}
},

updateProxyActiveTags: function (tags) {
var proxy;
var proxyTagSwitchFuture = new Future;
AppConfig.configureService('proxy', function (proxyService) {
try {
proxy = Follower.connect(proxyService.providers.proxy, {
group: "proxy"
});
proxy.call('updateTags', Ctl.myAppName(), tags);
proxy.disconnect();
if (!proxyTagSwitchFuture.isResolved())
proxyTagSwitchFuture['return']();
} catch (e) {
if (!proxyTagSwitchFuture.isResolved())
proxyTagSwitchFuture['throw'](e);
}
});

var proxyTimeout = Meteor.setTimeout(function () {
if (!proxyTagSwitchFuture.isResolved())
proxyTagSwitchFuture['throw'](
new Error("timed out looking for a proxy " +
"or trying to change tags on it " +
proxy.status().status));
}, 10*1000);
proxyTagSwitchFuture.wait();
Meteor.clearTimeout(proxyTimeout);
},

jobsCollection: _.once(function () {
return new Meteor.Collection("jobs", {manager: Ctl.findGalaxy()});
}),
Expand Down
2 changes: 1 addition & 1 deletion packages/ctl-helper/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Package.describe({
Npm.depends({optimist: '0.6.0'});

Package.on_use(function (api) {
api.use(['underscore', 'livedata', 'mongo-livedata', 'follower-livedata'], 'server');
api.use(['underscore', 'livedata', 'mongo-livedata', 'follower-livedata', 'application-configuration'], 'server');
api.export('Ctl', 'server');
api.add_files('ctl-helper.js', 'server');
});
119 changes: 74 additions & 45 deletions packages/ctl/ctl.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var Future = Npm.require("fibers/future");

Ctl.Commands.push({
name: "help",
func: function (argv) {
Expand Down Expand Up @@ -35,6 +37,10 @@ var startFun = function (argv) {
);
process.exit(1);
}
Ctl.subscribeToAppJobs(Ctl.myAppName());
var jobs = Ctl.jobsCollection();
var thisJob = jobs.findOne(Ctl.myJobId());
Ctl.updateProxyActiveTags(['', thisJob.star]);
if (Ctl.hasProgram("console")) {
console.log("starting console for app", Ctl.myAppName());
Ctl.startServerlikeProgramIfNotPresent("console", ["admin"], true);
Expand Down Expand Up @@ -89,58 +95,81 @@ Ctl.Commands.push({
func: stopFun
});

var waitForDone = function (jobCollection, jobId) {
var fut = new Future();
var found = false;
try {
var observation = jobCollection.find(jobId).observe({
added: function (doc) {
found = true;
if (doc.done)
fut['return']();
},
changed: function (doc) {
if (doc.done)
fut['return']();
},
removed: function (doc) {
fut['return']();
}
});
// if the document doesn't exist at all, it's certainly done.
if (!found)
fut['return']();
fut.wait();
} finally {
observation.stop();
}
};


Ctl.Commands.push({
name: "beginUpdate",
help: "Stop this app to begin an update",
func: stopFun
});

Ctl.Commands.push({
name: "scale",
help: "Scale jobs",
func: function (argv) {
if (argv.help || argv._.length === 0 || _.contains(argv._, 'ctl')) {
process.stderr.write(
"Usage: ctl scale program1=n [...] \n" +
"\n" +
"Scales some programs. Runs or kills jobs until there are n non-done jobs\n" +
"in that state.\n"
);
process.exit(1);
}

var scales = _.map(argv._, function (arg) {
var m = arg.match(/^(.+)=(\d+)$/);
if (!m) {
console.log("Bad scaling argument; should be program=number.");
process.exit(1);
}
return {program: m[1], scale: parseInt(m[2])};
Ctl.subscribeToAppJobs(Ctl.myAppName());
var jobs = Ctl.jobsCollection();
var thisJob = jobs.findOne(Ctl.myJobId());
// Look at all the server jobs that are on the old star.
var oldJobSelector = {
app: Ctl.myAppName(),
star: {$ne: thisJob.star},
program: "server",
done: false
};
var oldServers = jobs.find(oldJobSelector).fetch();
// Start a new job for each of them.
_.each(oldServers, function (oldServer) {
Ctl.startServerlikeProgram("server",
oldServer.tags,
oldServer.env.ADMIN_APP);
});

_.each(scales, function (s) {
var jobs = Ctl.getJobsByApp(
Ctl.myAppName(), {program: s.program, done: false});
jobs.forEach(function (job) {
--s.scale;
// Is this an extraneous job, more than the number that we need? Kill
// it!
if (s.scale < 0) {
Ctl.kill(s.program, job._id);
}
});
// Now start any jobs that are necessary.
if (s.scale <= 0)
return;
console.log("Starting %d jobs for %s", s.scale, s.program);
_.times(s.scale, function () {
// XXX args? env?
Ctl.prettyCall(Ctl.findGalaxy(), 'run', [Ctl.myAppName(), s.program, {
exitPolicy: 'restart'
}]);
});
// Wait for them all to come up and bind to the proxy.
Meteor._sleepForMs(5000); // XXX: Eventually make sure they're proxy-bound.
Ctl.updateProxyActiveTags(['', thisJob.star]);

// (eventually) tell the proxy to switch over to using the new star
// One by one, kill all the old star's server jobs.
var jobToKill = jobs.findOne(oldJobSelector);
while (jobToKill) {
Ctl.kill("server", jobToKill._id);
// Wait for it to go down
waitForDone(jobs, jobToKill._id);
// Spend some time in between to allow any reconnect storm to die down.
Meteor._sleepForMs(1000);
jobToKill = jobs.findOne(oldJobSelector);
}
// Now kill all old non-server jobs. They're less important.
jobs.find({
app: Ctl.myAppName(),
star: {$ne: thisJob.star},
program: {$ne: "server"},
done: false
}).forEach(function (job) {
Ctl.kill(job.program, job._id);
});
// fin
process.exit(0);
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/ctl/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Package.describe({
});

Package.on_use(function (api) {
api.use(['underscore', 'livedata', 'mongo-livedata', 'ctl-helper'], 'server');
api.use(['underscore', 'livedata', 'mongo-livedata', 'ctl-helper', 'application-configuration', 'follower-livedata'], 'server');
api.export('main', 'server');
api.add_files('ctl.js', 'server');
});
3 changes: 3 additions & 0 deletions packages/webapp/css_detect.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
._meteor_detect_css {
width: 0px;
}
3 changes: 3 additions & 0 deletions packages/webapp/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Npm.depends({connect: "2.9.0",

Package.on_use(function (api) {
api.use(['logging', 'underscore', 'routepolicy'], 'server');
api.use(['underscore'], 'client');
api.use(['application-configuration', 'follower-livedata'], {
unordered: true
});
Expand All @@ -19,4 +20,6 @@ Package.on_use(function (api) {
// loaded after webapp.
api.export(['WebApp', 'main', 'WebAppInternals'], 'server');
api.add_files('webapp_server.js', 'server');
api.add_files('webapp_client.js', 'client');
api.add_files('css_detect.css', 'client');
});
7 changes: 7 additions & 0 deletions packages/webapp/webapp_client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Meteor._isCssLoaded = function () {
return _.find(document.styleSheets, function (sheet) {
return _.find(sheet.cssRules, function (rule) {
return rule.selectorText === '._meteor_detect_css';
});
});
};
Loading

0 comments on commit f92cb7d

Please sign in to comment.