From 6b8333207b6527b46c6397630da9dcb3a4dbfc26 Mon Sep 17 00:00:00 2001 From: Michael Hackstein Date: Tue, 14 Jan 2014 14:52:52 +0100 Subject: [PATCH 1/4] Added first implementation of the Cluster Dashboard. Functionality is basic, displayed data is faked and design is comming soon --- js/apps/system/aardvark/aardvark.js | 2 +- .../frontend/css/clusterDashboardView.css | 6 ++ .../js/templates/clusterCollectionView.ejs | 3 + .../js/templates/clusterDashboardView.ejs | 28 ++++++ .../js/templates/clusterDatabaseView.ejs | 3 + .../js/templates/clusterOverviewView.ejs | 3 + .../js/templates/clusterServerView.ejs | 3 + .../js/templates/clusterShardsView.ejs | 3 + .../js/views/clusterCollectionView.js | 35 ++++++++ .../frontend/js/views/clusterDashboardView.js | 22 +++++ .../frontend/js/views/clusterDatabaseView.js | 33 +++++++ .../frontend/js/views/clusterOverviewView.js | 48 +++++++++++ .../frontend/js/views/clusterServerView.js | 35 ++++++++ .../frontend/js/views/clusterShardsView.js | 21 +++++ js/apps/system/aardvark/manifest.json | 1 + .../specs/views/clusterCollectionViewSpec.js | 86 +++++++++++++++++++ .../specs/views/clusterDashboardViewSpec.js | 51 +++++++++++ .../specs/views/clusterDatabaseViewSpec.js | 79 +++++++++++++++++ .../specs/views/clusterOverviewViewSpec.js | 85 ++++++++++++++++++ .../test/specs/views/clusterServerViewSpec.js | 84 ++++++++++++++++++ 20 files changed, 630 insertions(+), 1 deletion(-) create mode 100644 js/apps/system/aardvark/frontend/css/clusterDashboardView.css create mode 100644 js/apps/system/aardvark/frontend/js/templates/clusterCollectionView.ejs create mode 100644 js/apps/system/aardvark/frontend/js/templates/clusterDashboardView.ejs create mode 100644 js/apps/system/aardvark/frontend/js/templates/clusterDatabaseView.ejs create mode 100644 js/apps/system/aardvark/frontend/js/templates/clusterOverviewView.ejs create mode 100644 js/apps/system/aardvark/frontend/js/templates/clusterServerView.ejs create mode 100644 js/apps/system/aardvark/frontend/js/templates/clusterShardsView.ejs create mode 100644 js/apps/system/aardvark/frontend/js/views/clusterCollectionView.js create mode 100644 js/apps/system/aardvark/frontend/js/views/clusterDashboardView.js create mode 100644 js/apps/system/aardvark/frontend/js/views/clusterDatabaseView.js create mode 100644 js/apps/system/aardvark/frontend/js/views/clusterOverviewView.js create mode 100644 js/apps/system/aardvark/frontend/js/views/clusterServerView.js create mode 100644 js/apps/system/aardvark/frontend/js/views/clusterShardsView.js create mode 100644 js/apps/system/aardvark/test/specs/views/clusterCollectionViewSpec.js create mode 100644 js/apps/system/aardvark/test/specs/views/clusterDashboardViewSpec.js create mode 100644 js/apps/system/aardvark/test/specs/views/clusterDatabaseViewSpec.js create mode 100644 js/apps/system/aardvark/test/specs/views/clusterOverviewViewSpec.js create mode 100644 js/apps/system/aardvark/test/specs/views/clusterServerViewSpec.js diff --git a/js/apps/system/aardvark/aardvark.js b/js/apps/system/aardvark/aardvark.js index 88af5e0515..d80ad94158 100644 --- a/js/apps/system/aardvark/aardvark.js +++ b/js/apps/system/aardvark/aardvark.js @@ -45,7 +45,7 @@ var docus = new (require("lib/swagger").Swagger)(); controller.put("/foxxes/install", function (req, res) { var content = JSON.parse(req.requestBody), - name = content.name,- + name = content.name, mount = content.mount, version = content.version; res.json(foxxes.install(name, mount, version)); diff --git a/js/apps/system/aardvark/frontend/css/clusterDashboardView.css b/js/apps/system/aardvark/frontend/css/clusterDashboardView.css new file mode 100644 index 0000000000..024bd12458 --- /dev/null +++ b/js/apps/system/aardvark/frontend/css/clusterDashboardView.css @@ -0,0 +1,6 @@ +div.clusterColumn { + width: 18%; + padding-left: 1%; + padding-right: 1%; + float: left; +} diff --git a/js/apps/system/aardvark/frontend/js/templates/clusterCollectionView.ejs b/js/apps/system/aardvark/frontend/js/templates/clusterCollectionView.ejs new file mode 100644 index 0000000000..a29f755d5e --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/templates/clusterCollectionView.ejs @@ -0,0 +1,3 @@ + + + diff --git a/js/apps/system/aardvark/frontend/js/templates/clusterDashboardView.ejs b/js/apps/system/aardvark/frontend/js/templates/clusterDashboardView.ejs new file mode 100644 index 0000000000..92954c0a8f --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/templates/clusterDashboardView.ejs @@ -0,0 +1,28 @@ + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
diff --git a/js/apps/system/aardvark/frontend/js/templates/clusterDatabaseView.ejs b/js/apps/system/aardvark/frontend/js/templates/clusterDatabaseView.ejs new file mode 100644 index 0000000000..433fe983da --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/templates/clusterDatabaseView.ejs @@ -0,0 +1,3 @@ + + + diff --git a/js/apps/system/aardvark/frontend/js/templates/clusterOverviewView.ejs b/js/apps/system/aardvark/frontend/js/templates/clusterOverviewView.ejs new file mode 100644 index 0000000000..2b1b3f6510 --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/templates/clusterOverviewView.ejs @@ -0,0 +1,3 @@ + + + diff --git a/js/apps/system/aardvark/frontend/js/templates/clusterServerView.ejs b/js/apps/system/aardvark/frontend/js/templates/clusterServerView.ejs new file mode 100644 index 0000000000..126cd4eefa --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/templates/clusterServerView.ejs @@ -0,0 +1,3 @@ + + + diff --git a/js/apps/system/aardvark/frontend/js/templates/clusterShardsView.ejs b/js/apps/system/aardvark/frontend/js/templates/clusterShardsView.ejs new file mode 100644 index 0000000000..05b5d3b8d8 --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/templates/clusterShardsView.ejs @@ -0,0 +1,3 @@ + + + diff --git a/js/apps/system/aardvark/frontend/js/views/clusterCollectionView.js b/js/apps/system/aardvark/frontend/js/views/clusterCollectionView.js new file mode 100644 index 0000000000..d9d5b8cb62 --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/views/clusterCollectionView.js @@ -0,0 +1,35 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true*/ +/*global Backbone, templateEngine, $, window */ + +(function() { + "use strict"; + + window.ClusterCollectionView = Backbone.View.extend({ + + el: '#clusterCollections', + + template: templateEngine.createTemplate("clusterCollectionView.ejs"), + + events: { + "click .collection": "loadCollection" + }, + + initialize: function() { + this.shardsView = new window.ClusterShardsView(); + }, + + loadCollection: function(e) { + var id = e.currentTarget.id; + this.shardsView.render({ + name: id + }); + }, + + render: function(){ + $(this.el).html(this.template.render({})); + return this; + } + + }); + +}()); diff --git a/js/apps/system/aardvark/frontend/js/views/clusterDashboardView.js b/js/apps/system/aardvark/frontend/js/views/clusterDashboardView.js new file mode 100644 index 0000000000..71a598086e --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/views/clusterDashboardView.js @@ -0,0 +1,22 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true*/ +/*global Backbone, templateEngine, $, window */ + +(function() { + "use strict"; + + window.ClusterDashboardView = Backbone.View.extend({ + + el: '#content', + + template: templateEngine.createTemplate("clusterDashboardView.ejs"), + + render: function(){ + $(this.el).html(this.template.render({})); + this.overView = new window.ClusterOverviewView(); + this.overView.render(); + return this; + } + + }); + +}()); diff --git a/js/apps/system/aardvark/frontend/js/views/clusterDatabaseView.js b/js/apps/system/aardvark/frontend/js/views/clusterDatabaseView.js new file mode 100644 index 0000000000..6f491fce41 --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/views/clusterDatabaseView.js @@ -0,0 +1,33 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true*/ +/*global Backbone, templateEngine, $, window */ + +(function() { + "use strict"; + + window.ClusterDatabaseView = Backbone.View.extend({ + + el: '#clusterDatabases', + + template: templateEngine.createTemplate("clusterDatabaseView.ejs"), + + events: { + "click .database": "loadDatabase" + }, + + initialize: function() { + this.colView = new window.ClusterCollectionView(); + }, + + loadDatabase: function(e) { + var id = e.currentTarget.id; + this.colView.render(id); + }, + + render: function(){ + $(this.el).html(this.template.render({})); + return this; + } + + }); + +}()); diff --git a/js/apps/system/aardvark/frontend/js/views/clusterOverviewView.js b/js/apps/system/aardvark/frontend/js/views/clusterOverviewView.js new file mode 100644 index 0000000000..63ebe13ef2 --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/views/clusterOverviewView.js @@ -0,0 +1,48 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true */ +/*global Backbone, templateEngine, window, $ */ + +(function() { + "use strict"; + + window.ClusterOverviewView = Backbone.View.extend({ + + el: '#clusterOverview', + + template: templateEngine.createTemplate("clusterOverviewView.ejs"), + + events: { + "click #primary": "loadPrimaries", + "click #secondary": "loadSecondaries", + "click #coordinator": "loadCoordinators" + }, + + initialize: function() { + this.serverView = new window.ClusterServerView(); + }, + + loadPrimaries: function() { + this.serverView.render({ + type: "primary" + }); + }, + + loadSecondaries: function() { + this.serverView.render({ + type: "secondary" + }); + }, + + loadCoordinators: function() { + this.serverView.render({ + type: "coordinator" + }); + }, + + render: function(info){ + $(this.el).html(this.template.render({})); + return this; + } + + }); + +}()); diff --git a/js/apps/system/aardvark/frontend/js/views/clusterServerView.js b/js/apps/system/aardvark/frontend/js/views/clusterServerView.js new file mode 100644 index 0000000000..f9750ae409 --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/views/clusterServerView.js @@ -0,0 +1,35 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true*/ +/*global Backbone, templateEngine, $, window */ + +(function() { + "use strict"; + + window.ClusterServerView = Backbone.View.extend({ + + el: '#clusterServers', + + template: templateEngine.createTemplate("clusterServerView.ejs"), + + events: { + "click .server": "loadServer" + }, + + initialize: function() { + this.dbView = new window.ClusterDatabaseView(); + }, + + loadServer: function(e) { + var id = e.currentTarget.id; + this.dbView.render({ + name: id + }); + }, + + render: function(){ + $(this.el).html(this.template.render({})); + return this; + } + + }); + +}()); diff --git a/js/apps/system/aardvark/frontend/js/views/clusterShardsView.js b/js/apps/system/aardvark/frontend/js/views/clusterShardsView.js new file mode 100644 index 0000000000..91b3606bcc --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/views/clusterShardsView.js @@ -0,0 +1,21 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, vars: true, white: true, plusplus: true*/ +/*global Backbone, templateEngine, $, window */ + +(function() { + "use strict"; + + window.ClusterShardsView = Backbone.View.extend({ + + el: '#clusterShards', + + template: templateEngine.createTemplate("clusterShardsView.ejs"), + + render: function(){ + $(this.el).html(this.template.render({})); + return this; + } + + }); + +}()); + diff --git a/js/apps/system/aardvark/manifest.json b/js/apps/system/aardvark/manifest.json index 83232efc9c..28ad3f3427 100644 --- a/js/apps/system/aardvark/manifest.json +++ b/js/apps/system/aardvark/manifest.json @@ -131,6 +131,7 @@ "frontend/css/general.css", "frontend/css/buttons.css", "frontend/css/screenSizes.css", + "frontend/css/clusterDashboardView.css", "frontend/ttf/arangofont/style.css" ] } diff --git a/js/apps/system/aardvark/test/specs/views/clusterCollectionViewSpec.js b/js/apps/system/aardvark/test/specs/views/clusterCollectionViewSpec.js new file mode 100644 index 0000000000..c4fe60bf25 --- /dev/null +++ b/js/apps/system/aardvark/test/specs/views/clusterCollectionViewSpec.js @@ -0,0 +1,86 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true, browser: true*/ +/*global describe, beforeEach, afterEach, it, */ +/*global spyOn, expect*/ +/*global templateEngine, $*/ +(function() { + "use strict"; + + describe("Cluster Collection View", function() { + var view, div, shardsView; + + beforeEach(function() { + div = document.createElement("div"); + div.id = "clusterCollections"; + document.body.appendChild(div); + shardsView = { + render: function(){} + }; + spyOn(window, "ClusterShardsView").andReturn(shardsView); + }); + + afterEach(function() { + document.body.removeChild(div); + }); + + describe("initialisation", function() { + + it("should create a Cluster Server View", function() { + view = new window.ClusterCollectionView(); + expect(window.ClusterShardsView).toHaveBeenCalled(); + }); + + }); + + describe("rendering", function() { + + beforeEach(function() { + spyOn(shardsView, "render"); + view = new window.ClusterCollectionView(); + }); + + it("should not render the Server view", function() { + view.render(); + expect(shardsView.render).not.toHaveBeenCalled(); + }); + }); + + describe("user actions", function() { + var info; + + beforeEach(function() { + spyOn(shardsView, "render"); + view = new window.ClusterCollectionView(); + view.render(); + }); + + it("should be able to navigate to Documents", function() { + info = { + name: "Documents" + }; + $("#Documents").click(); + expect(shardsView.render).toHaveBeenCalledWith(info); + }); + + it("should be able to navigate to Edges", function() { + info = { + name: "Edges" + }; + $("#Edges").click(); + expect(shardsView.render).toHaveBeenCalledWith(info); + }); + + it("should be able to navigate to People", function() { + info = { + name: "People" + }; + $("#People").click(); + expect(shardsView.render).toHaveBeenCalledWith(info); + }); + + }); + + }); + +}()); + + diff --git a/js/apps/system/aardvark/test/specs/views/clusterDashboardViewSpec.js b/js/apps/system/aardvark/test/specs/views/clusterDashboardViewSpec.js new file mode 100644 index 0000000000..d8ae02225f --- /dev/null +++ b/js/apps/system/aardvark/test/specs/views/clusterDashboardViewSpec.js @@ -0,0 +1,51 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true, browser: true*/ +/*global describe, beforeEach, afterEach, it, */ +/*global spyOn, expect*/ +/*global templateEngine*/ +(function() { + "use strict"; + + describe("Cluster Dashboard View", function() { + var view, div, overview; + + beforeEach(function() { + div = document.createElement("div"); + div.id = "content"; + document.body.appendChild(div); + overview = { + render: function(){} + }; + spyOn(window, "ClusterOverviewView").andReturn(overview); + }); + + afterEach(function() { + document.body.removeChild(div); + }); + + describe("rendering", function() { + var info; + + beforeEach(function() { + spyOn(overview, "render"); + view = new window.ClusterDashboardView(); + }); + + it("should create a Cluster Overview View", function() { + expect(window.ClusterOverviewView).not.toHaveBeenCalled(); + window.ClusterOverviewView.reset(); + view.render(); + expect(window.ClusterOverviewView).toHaveBeenCalled(); + window.ClusterOverviewView.reset(); + view.render(); + expect(window.ClusterOverviewView).toHaveBeenCalled(); + }); + + it("should render the Cluster Overview", function() { + view.render(); + expect(overview.render).toHaveBeenCalled(); + }); + }); + + }); + +}()); diff --git a/js/apps/system/aardvark/test/specs/views/clusterDatabaseViewSpec.js b/js/apps/system/aardvark/test/specs/views/clusterDatabaseViewSpec.js new file mode 100644 index 0000000000..d03311ea6c --- /dev/null +++ b/js/apps/system/aardvark/test/specs/views/clusterDatabaseViewSpec.js @@ -0,0 +1,79 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true, browser: true*/ +/*global describe, beforeEach, afterEach, it, */ +/*global spyOn, expect*/ +/*global templateEngine, $*/ +(function() { + "use strict"; + + describe("Cluster Database View", function() { + var view, div, colView; + + beforeEach(function() { + div = document.createElement("div"); + div.id = "clusterDatabases"; + document.body.appendChild(div); + colView = { + render: function(){} + }; + spyOn(window, "ClusterCollectionView").andReturn(colView); + }); + + afterEach(function() { + document.body.removeChild(div); + }); + + describe("initialisation", function() { + + it("should create a Cluster Collection View", function() { + view = new window.ClusterDatabaseView(); + expect(window.ClusterCollectionView).toHaveBeenCalled(); + }); + + }); + + describe("rendering", function() { + + beforeEach(function() { + spyOn(colView, "render"); + view = new window.ClusterDatabaseView(); + }); + + it("should not render the Server view", function() { + view.render(); + expect(colView.render).not.toHaveBeenCalled(); + }); + }); + + describe("user actions", function() { + var db; + + beforeEach(function() { + spyOn(colView, "render"); + view = new window.ClusterDatabaseView(); + view.render(); + }); + + it("should be able to navigate to _system", function() { + db = "_system"; + $("#" + db).click(); + expect(colView.render).toHaveBeenCalledWith(db); + }); + + it("should be able to navigate to myDatabase", function() { + db = "myDatabase"; + $("#" + db).click(); + expect(colView.render).toHaveBeenCalledWith(db); + }); + + it("should be able to navigate to otherDatabase", function() { + db = "otherDatabase"; + $("#" + db).click(); + expect(colView.render).toHaveBeenCalledWith(db); + }); + + }); + + }); + +}()); + diff --git a/js/apps/system/aardvark/test/specs/views/clusterOverviewViewSpec.js b/js/apps/system/aardvark/test/specs/views/clusterOverviewViewSpec.js new file mode 100644 index 0000000000..71a5af4208 --- /dev/null +++ b/js/apps/system/aardvark/test/specs/views/clusterOverviewViewSpec.js @@ -0,0 +1,85 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true, browser: true*/ +/*global describe, beforeEach, afterEach, it, */ +/*global spyOn, expect*/ +/*global templateEngine, $*/ +(function() { + "use strict"; + + describe("Cluster Overview View", function() { + var view, div, serverView; + + beforeEach(function() { + div = document.createElement("div"); + div.id = "clusterOverview"; + document.body.appendChild(div); + serverView = { + render: function(){} + }; + spyOn(window, "ClusterServerView").andReturn(serverView); + }); + + afterEach(function() { + document.body.removeChild(div); + }); + + describe("initialisation", function() { + + it("should create a Cluster Server View", function() { + view = new window.ClusterOverviewView(); + expect(window.ClusterServerView).toHaveBeenCalled(); + }); + + }); + + describe("rendering", function() { + + beforeEach(function() { + spyOn(serverView, "render"); + view = new window.ClusterOverviewView(); + }); + + it("should not render the Server view", function() { + view.render(); + expect(serverView.render).not.toHaveBeenCalled(); + }); + }); + + describe("user actions", function() { + var info; + + beforeEach(function() { + spyOn(serverView, "render"); + view = new window.ClusterOverviewView(); + view.render(); + }); + + it("should be able to navigate to primary servers", function() { + info = { + type: "primary" + }; + $("#primary").click(); + expect(serverView.render).toHaveBeenCalledWith(info); + }); + + it("should be able to navigate to primary servers", function() { + info = { + type: "secondary" + }; + $("#secondary").click(); + expect(serverView.render).toHaveBeenCalledWith(info); + }); + + it("should be able to navigate to primary servers", function() { + info = { + type: "coordinator" + }; + $("#coordinator").click(); + expect(serverView.render).toHaveBeenCalledWith(info); + }); + + }); + + }); + +}()); + diff --git a/js/apps/system/aardvark/test/specs/views/clusterServerViewSpec.js b/js/apps/system/aardvark/test/specs/views/clusterServerViewSpec.js new file mode 100644 index 0000000000..d3674cef5a --- /dev/null +++ b/js/apps/system/aardvark/test/specs/views/clusterServerViewSpec.js @@ -0,0 +1,84 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true, browser: true*/ +/*global describe, beforeEach, afterEach, it, */ +/*global spyOn, expect*/ +/*global templateEngine, $*/ +(function() { + "use strict"; + + describe("Cluster Server View", function() { + var view, div, dbView; + + beforeEach(function() { + div = document.createElement("div"); + div.id = "clusterServers"; + document.body.appendChild(div); + dbView = { + render: function(){} + }; + spyOn(window, "ClusterDatabaseView").andReturn(dbView); + }); + + afterEach(function() { + document.body.removeChild(div); + }); + + describe("initialisation", function() { + + it("should create a Cluster Server View", function() { + view = new window.ClusterServerView(); + expect(window.ClusterDatabaseView).toHaveBeenCalled(); + }); + + }); + + describe("rendering", function() { + + beforeEach(function() { + spyOn(dbView, "render"); + view = new window.ClusterServerView(); + }); + + it("should not render the Server view", function() { + view.render(); + expect(dbView.render).not.toHaveBeenCalled(); + }); + }); + + describe("user actions", function() { + var info; + + beforeEach(function() { + spyOn(dbView, "render"); + view = new window.ClusterServerView(); + view.render(); + }); + + it("should be able to navigate to Pavel", function() { + info = { + name: "Pavel" + }; + $("#Pavel").click(); + expect(dbView.render).toHaveBeenCalledWith(info); + }); + + it("should be able to navigate to Peter", function() { + info = { + name: "Peter" + }; + $("#Peter").click(); + expect(dbView.render).toHaveBeenCalledWith(info); + }); + + it("should be able to navigate to Paul", function() { + info = { + name: "Paul" + }; + $("#Paul").click(); + expect(dbView.render).toHaveBeenCalledWith(info); + }); + + }); + + }); + +}()); From 8948b87f12966d2acf82f4c56b04b140619ac1b4 Mon Sep 17 00:00:00 2001 From: Michael Hackstein Date: Tue, 14 Jan 2014 14:55:52 +0100 Subject: [PATCH 2/4] Added the Cluster Dashboard to the karma tests --- js/apps/system/aardvark/test/karma/karma.conf.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/js/apps/system/aardvark/test/karma/karma.conf.js b/js/apps/system/aardvark/test/karma/karma.conf.js index fde6a555cf..210d9a3b08 100644 --- a/js/apps/system/aardvark/test/karma/karma.conf.js +++ b/js/apps/system/aardvark/test/karma/karma.conf.js @@ -167,6 +167,12 @@ module.exports = function(karma) { 'frontend/js/views/addNewGraphView.js', 'frontend/js/views/dbSelectionView.js', 'frontend/js/views/editListEntryView.js', + 'frontend/js/views/clusterDashboardView.js', + 'frontend/js/views/clusterOverviewView.js', + 'frontend/js/views/clusterServerView.js', + 'frontend/js/views/clusterDatabaseView.js', + 'frontend/js/views/clusterCollectionView.js', + 'frontend/js/views/clusterShardsView.js', // Router 'frontend/js/routers/router.js', @@ -174,6 +180,7 @@ module.exports = function(karma) { //Templates {pattern: 'frontend/js/templates/*.ejs', served:true, included:false, watched: true}, // Specs + // GraphViewer 'test/specs/graphViewer/specColourMapper/colourMapperSpec.js', 'test/specs/graphViewer/specWindowObjects/domObserverFactorySpec.js', @@ -208,6 +215,7 @@ module.exports = function(karma) { // Models 'test/specs/models/currentDatabaseSpec.js', 'test/specs/models/graphSpec.js', + // Views 'test/specs/views/editListEntryViewSpec.js', 'test/specs/views/collectionViewSpec.js', @@ -218,6 +226,12 @@ module.exports = function(karma) { 'test/specs/views/graphViewSpec.js', 'test/specs/views/graphManagementViewSpec.js', 'test/specs/views/addNewGraphViewSpec.js', + 'test/specs/views/clusterDashboardViewSpec.js', + 'test/specs/views/clusterOverviewViewSpec.js', + 'test/specs/views/clusterServerViewSpec.js', + 'test/specs/views/clusterDatabaseViewSpec.js', + 'test/specs/views/clusterCollectionViewSpec.js', + 'test/specs/views/clusterShardsViewSpec.js', // Router 'test/specs/router/routerSpec.js', // JSLint From e4976723b6b1dcc70f47a83f23abb74abe0a8768 Mon Sep 17 00:00:00 2001 From: Michael Hackstein Date: Tue, 14 Jan 2014 14:57:25 +0100 Subject: [PATCH 3/4] Added the cluster dashboard to hidden route, will not yet be reachable via navigation --- .../system/aardvark/frontend/js/routers/router.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/js/apps/system/aardvark/frontend/js/routers/router.js b/js/apps/system/aardvark/frontend/js/routers/router.js index 8a73e359fd..721842c866 100644 --- a/js/apps/system/aardvark/frontend/js/routers/router.js +++ b/js/apps/system/aardvark/frontend/js/routers/router.js @@ -29,7 +29,17 @@ "application/documentation/:key" : "appDocumentation", "graph" : "graph", "graphManagement" : "graphManagement", - "graphManagement/add" : "graphAddNew" + "graphManagement/add" : "graphAddNew", + + + "test" : "test" + }, + + test: function() { + if(!this.clusterDashboardView) { + this.clusterDashboardView = new window.ClusterDashboardView(); + } + this.clusterDashboardView.render(); }, initialize: function () { From fc96df2244a5fb4393cbc6d4a58c5ad772574b70 Mon Sep 17 00:00:00 2001 From: Max Neunhoeffer Date: Tue, 14 Jan 2014 15:04:55 +0100 Subject: [PATCH 4/4] Separate out ConnectionManager from ClusterComm. --- arangod/Cluster/ApplicationCluster.cpp | 4 + arangod/Cluster/ClusterComm.cpp | 448 +++++------------- arangod/Cluster/ClusterComm.h | 95 ---- arangod/V8Server/v8-vocbase.cpp | 99 +++- lib/Makefile.files | 6 +- .../GeneralClientConnection.h | 45 -- 6 files changed, 210 insertions(+), 487 deletions(-) diff --git a/arangod/Cluster/ApplicationCluster.cpp b/arangod/Cluster/ApplicationCluster.cpp index d3fed81ec3..76dee178ec 100644 --- a/arangod/Cluster/ApplicationCluster.cpp +++ b/arangod/Cluster/ApplicationCluster.cpp @@ -27,6 +27,7 @@ #include "ApplicationCluster.h" #include "Rest/Endpoint.h" +#include "SimpleHttpClient/ConnectionManager.h" #include "Cluster/HeartbeatThread.h" #include "Cluster/ServerState.h" #include "Cluster/ClusterInfo.h" @@ -201,6 +202,9 @@ bool ApplicationCluster::start () { ServerState::instance()->setState(ServerState::STATE_STARTUP); + // initialise ConnectionManager library + httpclient::ConnectionManager::instance()->initialise(); + // the agency about our state AgencyComm comm; comm.sendServerState(); diff --git a/arangod/Cluster/ClusterComm.cpp b/arangod/Cluster/ClusterComm.cpp index 87496301ed..3b1d0c1a8b 100644 --- a/arangod/Cluster/ClusterComm.cpp +++ b/arangod/Cluster/ClusterComm.cpp @@ -31,6 +31,7 @@ #include "Basics/WriteLocker.h" #include "Basics/ConditionLocker.h" #include "Basics/StringUtils.h" +#include "lib/SimpleHttpClient/ConnectionManager.h" #include "VocBase/server.h" @@ -40,18 +41,6 @@ using namespace triagens::arango; // --SECTION-- ClusterComm connection options // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @brief global options for connections -//////////////////////////////////////////////////////////////////////////////// - -ClusterCommOptions ClusterComm::_globalConnectionOptions = { - 15.0, // connectTimeout - 3.0, // requestTimeout - 3, // numRetries - 5.0, // singleRequestTimeout - 0 // sslProtocol -}; - //////////////////////////////////////////////////////////////////////////////// /// @brief global callback for asynchronous REST handler //////////////////////////////////////////////////////////////////////////////// @@ -90,11 +79,6 @@ ClusterComm::~ClusterComm () { delete _backgroundThread; _backgroundThread = 0; cleanupAllQueues(); - WRITE_LOCKER(allLock); - map::iterator i; - for (i = allConnections.begin(); i != allConnections.end(); ++i) { - delete i->second; - } } //////////////////////////////////////////////////////////////////////////////// @@ -133,229 +117,6 @@ OperationID ClusterComm::getOperationID () { return TRI_NewTickServer(); } -//////////////////////////////////////////////////////////////////////////////// -/// @brief destructor for SingleServerConnection class -//////////////////////////////////////////////////////////////////////////////// - -ClusterComm::SingleServerConnection::~SingleServerConnection () { - delete connection; - delete endpoint; - lastUsed = 0; - serverID = ""; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief destructor of ServerConnections class -//////////////////////////////////////////////////////////////////////////////// - -ClusterComm::ServerConnections::~ServerConnections () { - vector::iterator i; - WRITE_LOCKER(lock); - - unused.clear(); - for (i = connections.begin();i != connections.end();++i) { - delete *i; - } - connections.clear(); -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief open or get a previously cached connection to a server -//////////////////////////////////////////////////////////////////////////////// - -ClusterComm::SingleServerConnection* -ClusterComm::getConnection(ServerID& serverID) { - map::iterator i; - ServerConnections* s; - SingleServerConnection* c; - - // First find a connections list: - { - WRITE_LOCKER(allLock); - - i = allConnections.find(serverID); - if (i != allConnections.end()) { - s = i->second; - } - else { - s = new ServerConnections(); - allConnections[serverID] = s; - } - } - - assert(s != 0); - - // Now get an unused one: - { - WRITE_LOCKER(s->lock); - if (!s->unused.empty()) { - c = s->unused.back(); - s->unused.pop_back(); - return c; - } - } - - // We need to open a new one: - string a = ClusterInfo::instance()->getServerEndpoint(serverID); - - if (a == "") { - // Unknown server address, probably not yet connected - return 0; - } - triagens::rest::Endpoint* e = triagens::rest::Endpoint::clientFactory(a); - if (0 == e) { - return 0; - } - triagens::httpclient::GeneralClientConnection* - g = triagens::httpclient::GeneralClientConnection::factory( - e, - _globalConnectionOptions._requestTimeout, - _globalConnectionOptions._connectTimeout, - _globalConnectionOptions._connectRetries, - _globalConnectionOptions._sslProtocol); - if (0 == g) { - delete e; - return 0; - } - c = new SingleServerConnection(g,e,serverID); - if (0 == c) { - delete g; - delete e; - return 0; - } - - // Now put it into our administration: - { - WRITE_LOCKER(s->lock); - s->connections.push_back(c); - } - c->lastUsed = time(0); - return c; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief return leased connection to a server -//////////////////////////////////////////////////////////////////////////////// - -void ClusterComm::returnConnection(SingleServerConnection* c) { - map::iterator i; - ServerConnections* s; - - // First find the collections list: - { - WRITE_LOCKER(allLock); - - i = allConnections.find(c->serverID); - if (i != allConnections.end()) { - s = i->second; - } - else { - // How strange! We just destroy the connection in despair! - delete c; - return; - } - } - - c->lastUsed = time(0); - - // Now mark it as unused: - { - WRITE_LOCKER(s->lock); - s->unused.push_back(c); - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief report a leased connection as being broken -//////////////////////////////////////////////////////////////////////////////// - -void ClusterComm::brokenConnection(SingleServerConnection* c) { - map::iterator i; - ServerConnections* s; - - // First find the collections list: - { - WRITE_LOCKER(allLock); - - i = allConnections.find(c->serverID); - if (i != allConnections.end()) { - s = i->second; - } - else { - // How strange! We just destroy the connection in despair! - delete c; - return; - } - } - - // Now find it to get rid of it: - { - WRITE_LOCKER(s->lock); - vector::iterator i; - for (i = s->connections.begin(); i != s->connections.end(); ++i) { - if (*i == c) { - // Got it, now remove it: - s->connections.erase(i); - delete c; - return; - } - } - } - - // How strange! We should have known this one! - delete c; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief closes all connections that have been unused for more than -/// limit seconds -//////////////////////////////////////////////////////////////////////////////// - -void ClusterComm::closeUnusedConnections (double limit) { - WRITE_LOCKER(allLock); - map::iterator s; - list::iterator i; - list::iterator prev; - ServerConnections* sc; - time_t t; - bool haveprev; - - t = time(0); - for (s = allConnections.begin(); s != allConnections.end(); ++s) { - sc = s->second; - { - WRITE_LOCKER(sc->lock); - haveprev = false; - for (i = sc->unused.begin(); i != sc->unused.end(); ) { - if (t - (*i)->lastUsed > limit) { - vector::iterator j; - for (j = sc->connections.begin(); j != sc->connections.end(); ++j) { - if (*j == *i) { - sc->connections.erase(j); - break; - } - } - delete (*i); - sc->unused.erase(i); - if (haveprev) { - i = prev; // will be incremented in next iteration - i++; - haveprev = false; - } - else { - i = sc->unused.begin(); - } - } - else { - prev = i; - ++i; - haveprev = true; - } - } - } - } -} - //////////////////////////////////////////////////////////////////////////////// /// @brief submit an HTTP request to a shard asynchronously. /// @@ -438,7 +199,8 @@ ClusterCommResult* ClusterComm::asyncRequest ( } op->headerFields = headerFields; op->callback = callback; - op->endTime = timeout == 0.0 ? now()+24*60*60.0 : now()+timeout; + op->endTime = timeout == 0.0 ? TRI_microtime()+24*60*60.0 + : TRI_microtime()+timeout; ClusterCommResult* res = new ClusterCommResult(); *res = *static_cast(op); @@ -498,7 +260,7 @@ ClusterCommResult* ClusterComm::syncRequest ( body = 0; } - double currentTime = now(); + double currentTime = TRI_microtime(); double endTime = timeout == 0.0 ? currentTime+24*60*60.0 : currentTime+timeout; @@ -521,52 +283,61 @@ ClusterCommResult* ClusterComm::syncRequest ( } // We need a connection to this server: - SingleServerConnection* connection = getConnection(res->serverID); - if (0 == connection) { + string endpoint = ClusterInfo::instance()->getServerEndpoint(res->serverID); + if (endpoint == "") { res->status = CL_COMM_ERROR; - LOG_ERROR("cannot create connection to server '%s'", - res->serverID.c_str()); + LOG_ERROR("cannot find endpoint of server '%s'", res->serverID.c_str()); } else { - if (0 != body) { - LOG_DEBUG("sending %s request to DB server '%s': %s", - triagens::rest::HttpRequest::translateMethod(reqtype).c_str(), - res->serverID.c_str(), body); - } - else { - LOG_DEBUG("sending %s request to DB server '%s'", - triagens::rest::HttpRequest::translateMethod(reqtype).c_str(), - res->serverID.c_str()); - } - triagens::httpclient::SimpleHttpClient* client - = new triagens::httpclient::SimpleHttpClient( - connection->connection, - endTime-currentTime, false); - - res->result = client->request(reqtype, path, body, bodyLength, - headerFields); - if (res->result == 0 || ! res->result->isComplete()) { - brokenConnection(connection); + httpclient::ConnectionManager* cm + = httpclient::ConnectionManager::instance(); + httpclient::ConnectionManager::SingleServerConnection* connection + = cm->leaseConnection(endpoint); + if (0 == connection) { res->status = CL_COMM_ERROR; + LOG_ERROR("cannot create connection to server '%s'", + res->serverID.c_str()); } else { - returnConnection(connection); - if (res->result->wasHttpError()) { - res->status = CL_COMM_ERROR; + if (0 != body) { + LOG_DEBUG("sending %s request to DB server '%s': %s", + triagens::rest::HttpRequest::translateMethod(reqtype).c_str(), + res->serverID.c_str(), body); } - else if (client->getErrorMessage() == - "Request timeout reached") { - res->status = CL_COMM_TIMEOUT; + else { + LOG_DEBUG("sending %s request to DB server '%s'", + triagens::rest::HttpRequest::translateMethod(reqtype).c_str(), + res->serverID.c_str()); } - else if (client->getErrorMessage() != "") { - res->status = CL_COMM_ERROR; + triagens::httpclient::SimpleHttpClient* client + = new triagens::httpclient::SimpleHttpClient( + connection->connection, + endTime-currentTime, false); + client->keepConnectionOnDestruction(true); + + res->result = client->request(reqtype, path, body, bodyLength, + headerFields); + if (! res->result->isComplete()) { + cm->brokenConnection(connection); + if (client->getErrorMessage() == "Request timeout reached") { + res->status = CL_COMM_TIMEOUT; + } + else { + res->status = CL_COMM_ERROR; + } } + else { + cm->returnConnection(connection); + if (res->result->wasHttpError()) { + res->status = CL_COMM_ERROR; + } + } + delete client; + } + if (res->status == CL_COMM_SENDING) { + // Everything was OK + res->status = CL_COMM_SENT; } - delete client; - } - if (res->status == CL_COMM_SENDING) { - // Everything was OK - res->status = CL_COMM_SENT; } return res; } @@ -688,7 +459,7 @@ ClusterCommResult* ClusterComm::wait ( endtime = 1.0e50; // this is the Sankt Nimmerleinstag } else { - endtime = now() + timeout; + endtime = TRI_microtime() + timeout; } if (0 != operationID) { @@ -722,7 +493,7 @@ ClusterCommResult* ClusterComm::wait ( // It is in the receive queue but still waiting, now wait actually } // Here it could either be in the receive or the send queue, let's wait - timeleft = endtime - now(); + timeleft = endtime - TRI_microtime(); if (timeleft <= 0) break; somethingReceived.wait(uint64_t(timeleft * 1000000.0)); } @@ -773,7 +544,7 @@ ClusterCommResult* ClusterComm::wait ( return res; } // Here it could either be in the receive or the send queue, let's wait - timeleft = endtime - now(); + timeleft = endtime - TRI_microtime(); if (timeleft <= 0) break; somethingReceived.wait(uint64_t(timeleft * 1000000.0)); } @@ -882,8 +653,16 @@ void ClusterComm::asyncAnswer (string& coordinatorHeader, coordinatorID = coordinatorHeader.substr(start,pos-start); // Now find the connection to which the request goes from the coordinatorID: - ClusterComm::SingleServerConnection* connection - = getConnection(coordinatorID); + httpclient::ConnectionManager* cm = httpclient::ConnectionManager::instance(); + string endpoint = ClusterInfo::instance()->getServerEndpoint(coordinatorID); + if (endpoint == "") { + LOG_ERROR("asyncAnswer: cannot find endpoint for server '%s'", + coordinatorID.c_str()); + return; + } + + httpclient::ConnectionManager::SingleServerConnection* connection + = cm->leaseConnection(endpoint); if (0 == connection) { LOG_ERROR("asyncAnswer: cannot create connection to server '%s'", coordinatorID.c_str()); @@ -902,24 +681,23 @@ void ClusterComm::asyncAnswer (string& coordinatorHeader, triagens::httpclient::SimpleHttpClient* client = new triagens::httpclient::SimpleHttpClient( - connection->connection, - _globalConnectionOptions._singleRequestTimeout, - false); + connection->connection, 3600.0, false); + client->keepConnectionOnDestruction(true); // We add this result to the operation struct without acquiring // a lock, since we know that only we do such a thing: httpclient::SimpleHttpResult* result = client->request(rest::HttpRequest::HTTP_REQUEST_PUT, "/_api/shard-comm", body, len, headers); - if (client->getErrorMessage() != "") { - brokenConnection(connection); + if (! result->isComplete()) { + cm->brokenConnection(connection); } else { - returnConnection(connection); + cm->returnConnection(connection); } + // We cannot deal with a bad result here, so forget about it in any case. delete result; delete client; - returnConnection(connection); } //////////////////////////////////////////////////////////////////////////////// @@ -1036,8 +814,8 @@ bool ClusterComm::moveFromSendToReceived (OperationID operationID) { } if (op->status == CL_COMM_SENDING) { // Note that in the meantime the status could have changed to - // CL_COMM_ERROR or indeed to CL_COMM_RECEIVED in these cases, we do - // not want to overwrite this result + // CL_COMM_ERROR, CL_COMM_TIMEOUT or indeed to CL_COMM_RECEIVED in + // these cases, we do not want to overwrite this result op->status = CL_COMM_SENT; } received.push_back(op); @@ -1141,7 +919,7 @@ void ClusterCommThread::run () { // sent the request (happens in moveFromSendToReceived). // Have we already reached the timeout? - double currentTime = cc->now(); + double currentTime = TRI_microtime(); if (op->endTime <= currentTime) { op->status = CL_COMM_TIMEOUT; } @@ -1151,53 +929,63 @@ void ClusterCommThread::run () { } else { // We need a connection to this server: - ClusterComm::SingleServerConnection* connection - = cc->getConnection(op->serverID); - if (0 == connection) { + string endpoint + = ClusterInfo::instance()->getServerEndpoint(op->serverID); + if (endpoint == "") { op->status = CL_COMM_ERROR; - LOG_ERROR("cannot create connection to server '%s'", + LOG_ERROR("cannot find endpoint for server '%s'", op->serverID.c_str()); } else { - if (0 != op->body) { - LOG_DEBUG("sending %s request to DB server '%s': %s", - triagens::rest::HttpRequest::translateMethod(op->reqtype) - .c_str(), op->serverID.c_str(), op->body); - } - else { - LOG_DEBUG("sending %s request to DB server '%s'", - triagens::rest::HttpRequest::translateMethod(op->reqtype) - .c_str(), op->serverID.c_str()); - } - - triagens::httpclient::SimpleHttpClient* client - = new triagens::httpclient::SimpleHttpClient( - connection->connection, - op->endTime-currentTime, false); - - // We add this result to the operation struct without acquiring - // a lock, since we know that only we do such a thing: - op->result = client->request(op->reqtype, op->path, op->body, - op->bodyLength, *(op->headerFields)); - - if (op->result == 0 || ! op->result->isComplete()) { - cc->brokenConnection(connection); + httpclient::ConnectionManager* cm + = httpclient::ConnectionManager::instance(); + httpclient::ConnectionManager::SingleServerConnection* connection + = cm->leaseConnection(endpoint); + if (0 == connection) { op->status = CL_COMM_ERROR; + LOG_ERROR("cannot create connection to server '%s'", + op->serverID.c_str()); } else { - cc->returnConnection(connection); - if (op->result->wasHttpError()) { - op->status = CL_COMM_ERROR; + if (0 != op->body) { + LOG_DEBUG("sending %s request to DB server '%s': %s", + triagens::rest::HttpRequest::translateMethod(op->reqtype) + .c_str(), op->serverID.c_str(), op->body); } - else if (client->getErrorMessage() == - "Request timeout reached") { - op->status = CL_COMM_TIMEOUT; + else { + LOG_DEBUG("sending %s request to DB server '%s'", + triagens::rest::HttpRequest::translateMethod(op->reqtype) + .c_str(), op->serverID.c_str()); } - else if (client->getErrorMessage() != "") { - op->status = CL_COMM_ERROR; + + triagens::httpclient::SimpleHttpClient* client + = new triagens::httpclient::SimpleHttpClient( + connection->connection, + op->endTime-currentTime, false); + client->keepConnectionOnDestruction(true); + + // We add this result to the operation struct without acquiring + // a lock, since we know that only we do such a thing: + op->result = client->request(op->reqtype, op->path, op->body, + op->bodyLength, *(op->headerFields)); + + if (! op->result->isComplete()) { + cm->brokenConnection(connection); + if (client->getErrorMessage() == "Request timeout reached") { + op->status = CL_COMM_TIMEOUT; + } + else { + op->status = CL_COMM_ERROR; + } } + else { + cm->returnConnection(connection); + if (op->result->wasHttpError()) { + op->status = CL_COMM_ERROR; + } + } + delete client; } - delete client; } } } @@ -1212,7 +1000,7 @@ void ClusterCommThread::run () { // just now, so we can check on our receive queue to detect timeouts: { - double currentTime = cc->now(); + double currentTime = TRI_microtime(); basics::ConditionLocker locker(&cc->somethingReceived); ClusterComm::QueueIterator q; for (q = cc->received.begin(); q != cc->received.end(); ++q) { diff --git a/arangod/Cluster/ClusterComm.h b/arangod/Cluster/ClusterComm.h index 6c4d8b9b9d..1002a10d66 100644 --- a/arangod/Cluster/ClusterComm.h +++ b/arangod/Cluster/ClusterComm.h @@ -191,17 +191,6 @@ namespace triagens { } }; -//////////////////////////////////////////////////////////////////////////////// -/// @brief options for cluster operations -//////////////////////////////////////////////////////////////////////////////// - - struct ClusterCommOptions { - double _connectTimeout; - double _requestTimeout; - size_t _connectRetries; - double _singleRequestTimeout; - uint32_t _sslProtocol; - }; //////////////////////////////////////////////////////////////////////////////// /// @brief global callback for asynchronous REST handler @@ -353,96 +342,12 @@ void ClusterCommRestCallback(string& coordinator, rest::HttpResponse* response); static ClusterComm* _theinstance; -//////////////////////////////////////////////////////////////////////////////// -/// @brief global options for connections -//////////////////////////////////////////////////////////////////////////////// - - static ClusterCommOptions _globalConnectionOptions; - //////////////////////////////////////////////////////////////////////////////// /// @brief produces an operation ID which is unique in this process //////////////////////////////////////////////////////////////////////////////// static OperationID getOperationID (); -//////////////////////////////////////////////////////////////////////////////// -/// @brief get timestamp -//////////////////////////////////////////////////////////////////////////////// - - static double now () { - struct timeval tv; - gettimeofday(&tv, 0); - - double sec = (double) tv.tv_sec; // seconds - double usc = (double) tv.tv_usec; // microseconds - - return sec + usc / 1000000.0; - } - -//////////////////////////////////////////////////////////////////////////////// -/// @brief class to administrate one connection to a server -//////////////////////////////////////////////////////////////////////////////// - - struct SingleServerConnection { - httpclient::GeneralClientConnection* connection; - rest::Endpoint* endpoint; - time_t lastUsed; - ServerID serverID; - - SingleServerConnection (httpclient::GeneralClientConnection* c, - rest::Endpoint* e, - ServerID s) - : connection(c), endpoint(e), lastUsed(0), serverID(s) {} - ~SingleServerConnection (); - }; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief class to administrate all connections to a server -//////////////////////////////////////////////////////////////////////////////// - - struct ServerConnections { - vector connections; - list unused; - triagens::basics::ReadWriteLock lock; - - ServerConnections () {} - ~ServerConnections (); // closes all connections - }; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief map to store all connections to all servers with corresponding lock -//////////////////////////////////////////////////////////////////////////////// - - // We keep connections to servers open but do not care - // if they are closed. The key is the server ID. - map allConnections; - triagens::basics::ReadWriteLock allLock; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief open or get a previously cached connection to a server -//////////////////////////////////////////////////////////////////////////////// - - SingleServerConnection* getConnection(ServerID& serverID); - -//////////////////////////////////////////////////////////////////////////////// -/// @brief return leased connection to a server -//////////////////////////////////////////////////////////////////////////////// - - void returnConnection(SingleServerConnection* singleConnection); - -//////////////////////////////////////////////////////////////////////////////// -/// @brief report a leased connection as being broken -//////////////////////////////////////////////////////////////////////////////// - - void brokenConnection(SingleServerConnection* singleConnection); - -//////////////////////////////////////////////////////////////////////////////// -/// @brief closes all connections that have been unused for more than -/// limit seconds -//////////////////////////////////////////////////////////////////////////////// - - void closeUnusedConnections(double limit); - //////////////////////////////////////////////////////////////////////////////// /// @brief send queue with lock and index //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 2197572852..5d3d97a1a2 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -8159,7 +8159,6 @@ static v8::Handle JS_ListDatabases (v8::Arguments const& argv) { #ifdef TRI_ENABLE_CLUSTER - //////////////////////////////////////////////////////////////////////////////// /// @brief helper function for the agency /// @@ -8167,10 +8166,16 @@ static v8::Handle JS_ListDatabases (v8::Arguments const& argv) { /// name. //////////////////////////////////////////////////////////////////////////////// -static int CreateDatabaseInAgency(string const& place, string const& name) { +static int CreateDatabaseInAgency(string const& place, string const& name, + vector* DBServers) { AgencyComm ac; AgencyCommLocker locker(place,"WRITE"); AgencyCommResult res; + if (0 != DBServers) { + ClusterInfo* ci = ClusterInfo::instance(); + ci->loadDBServers(); // to make sure we know about all of them + *DBServers = ci->getDBServers(); + } res = ac.casValue(place+"/Collections/"+name+"/Lock",string("UNLOCKED"), false, 0.0, 0.0); if (res.successful()) { @@ -8224,17 +8229,22 @@ static v8::Handle JS_CreateDatabase_Coordinator (v8::Arguments const& const string name = TRI_ObjectToString(argv[0]); - ClusterInfo* ci = ClusterInfo::instance(); + //ClusterInfo* ci = ClusterInfo::instance(); ClusterComm* cc = ClusterComm::instance(); AgencyComm ac; int ourerrno = TRI_ERROR_NO_ERROR; - ourerrno = CreateDatabaseInAgency("Target",name); + ourerrno = CreateDatabaseInAgency("Target",name,0); if (ourerrno == TRI_ERROR_NO_ERROR) { // everything OK in /Target - ourerrno = CreateDatabaseInAgency("Plan",name); + vector DBServers; + // We will get the list of DBServers whilst holding the lock to + // modify "/Plan/Collections". Therefore, everybody who is on the + // list will be told, everybody who is starting later will see the + // entry in "/Plan/Collections/..." and will create the database on + // startup. + ourerrno = CreateDatabaseInAgency("Plan",name,&DBServers); if (ourerrno == TRI_ERROR_NO_ERROR) { - vector DBServers = ci->getDBServers(); vector::iterator it; // build request to be sent to all servers @@ -8253,44 +8263,37 @@ static v8::Handle JS_CreateDatabase_Coordinator (v8::Arguments const& jsonstr.size(), new map, 0, 0.0); delete res; } - cout << "CDB: Have sent " << DBServers.size() << " requests." << endl; unsigned int done = 0; while (done < DBServers.size()) { res = cc->wait("", coordTransactionID, 0, "", 0.0); if (res->status == CL_COMM_RECEIVED) { if (res->answer_code == triagens::rest::HttpResponse::OK) { - cout << "CDB: answer OK" << endl; done++; delete res; } else if (res->answer_code == triagens::rest::HttpResponse::CONFLICT) { - cout << "CDB: answer CONFLICT" << endl; ourerrno = TRI_ERROR_ARANGO_DUPLICATE_NAME; delete res; break; } else { - cout << "CDB: answer BAD" << endl; ourerrno = TRI_ERROR_INTERNAL; delete res; break; } } else { - cout << "CDB: CL_COMM_ERROR" << endl; delete res; break; } } if (done == DBServers.size()) { - ourerrno = CreateDatabaseInAgency("Current",name); + ourerrno = CreateDatabaseInAgency("Current",name,0); if (ourerrno == TRI_ERROR_NO_ERROR) { - cout << "CDB: All done" << endl; return scope.Close(v8::True()); } } cc->drop( "CreateDatabase", coordTransactionID, 0, "" ); - cout << "CDB: Aborting..." << endl; for (it = DBServers.begin(); it != DBServers.end(); ++it) { res = cc->asyncRequest("CreateDB", coordTransactionID, "server:"+*it, @@ -8302,7 +8305,6 @@ static v8::Handle JS_CreateDatabase_Coordinator (v8::Arguments const& done = 0; while (done < DBServers.size()) { res = cc->wait("", coordTransactionID, 0, "", 0.0); - cout << "CDB: Got answer" << endl; delete res; done++; } @@ -8470,6 +8472,66 @@ static v8::Handle JS_CreateDatabase (v8::Arguments const& argv) { return scope.Close(v8::True()); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief drop a database, case of a coordinator in a cluster +//////////////////////////////////////////////////////////////////////////////// + +#ifdef TRI_ENABLE_CLUSTER + +static v8::Handle JS_DropDatabase_Coordinator (v8::Arguments const& argv) { + v8::HandleScope scope; + + // Arguments are already checked, there is exactly one argument + + const string name = TRI_ObjectToString(argv[0]); + + ClusterInfo* ci = ClusterInfo::instance(); + ClusterComm* cc = ClusterComm::instance(); + AgencyComm ac; + AgencyCommResult acres; + + int ourerrno = TRI_ERROR_NO_ERROR; + + { + AgencyCommLocker locker("Target","WRITE"); + // FIXME: need to check that locking worked! + + // Now nobody can create or remove a database, so we can check that + // the one we want to drop does indeed exist: + acres = ac.getValues("Current/Collections/"+name+"/Lock", false); + if (!acres.successful()) { + TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); + } + } + + // Now let's lock it. + // We cannot use a locker here, because we want to remove all of + // Current/Collections/ before we are done and we must not + // unlock the Lock after that. + if (!ac.lockWrite("Current/Collections/"+name, 24*3600.0, 24*3600.0)) { + TRI_V8_EXCEPTION(scope, TRI_ERROR_INTERNAL); + } + // res = ac.getValues("Current/Collections/"+name+"/Lock, false); + + // If this fails or the DB does not exist, return an error + // Remove entry Plan/Collections/ using Plan/Lock + // get list of DBServers during the lock + // (from now on new DBServers will no longer create a database) + // this is the point of no return + // tell all DBServers to drop database + // note errors, but there is nothing we can do about it if things go wrong + // only count and reports the servers with errors + // Remove entry Target/Collections/, use Target/Lock + // Remove entry Current/Collections/ using Current/Lock + // (from now on coordinators will understand that the database is gone + // Release Plan/Lock + // Report error + + return scope.Close(v8::True()); +} + +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief drop an existing database /// @@ -8502,6 +8564,13 @@ static v8::Handle JS_DropDatabase (v8::Arguments const& argv) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE); } +#ifdef TRI_ENABLE_CLUSTER + // If we are a coordinator in a cluster, we have to behave differently: + if (ServerState::instance()->isCoordinator()) { + return JS_DropDatabase_Coordinator(argv); + } +#endif + const string name = TRI_ObjectToString(argv[0]); TRI_v8_global_t* v8g = (TRI_v8_global_t*) v8::Isolate::GetCurrent()->GetData(); diff --git a/lib/Makefile.files b/lib/Makefile.files index f772fa331d..46e3fc9cba 100644 --- a/lib/Makefile.files +++ b/lib/Makefile.files @@ -106,7 +106,8 @@ lib_libarango_client_a_SOURCES = \ lib/SimpleHttpClient/ClientConnection.cpp \ lib/SimpleHttpClient/SslClientConnection.cpp \ lib/SimpleHttpClient/SimpleHttpClient.cpp \ - lib/SimpleHttpClient/SimpleHttpResult.cpp + lib/SimpleHttpClient/SimpleHttpResult.cpp \ + lib/SimpleHttpClient/ConnectionManager.cpp ################################################################################ ### @brief library "libarango.a", front-end part @@ -162,7 +163,8 @@ lib_libarango_v8_a_SOURCES = \ lib/SimpleHttpClient/ClientConnection.cpp \ lib/SimpleHttpClient/SslClientConnection.cpp \ lib/SimpleHttpClient/SimpleHttpClient.cpp \ - lib/SimpleHttpClient/SimpleHttpResult.cpp + lib/SimpleHttpClient/SimpleHttpResult.cpp \ + lib/SimpleHttpClient/ConnectionManager.cpp ################################################################################ diff --git a/lib/SimpleHttpClient/GeneralClientConnection.h b/lib/SimpleHttpClient/GeneralClientConnection.h index 6991405388..c642d5d6ce 100644 --- a/lib/SimpleHttpClient/GeneralClientConnection.h +++ b/lib/SimpleHttpClient/GeneralClientConnection.h @@ -50,11 +50,6 @@ namespace triagens { // --SECTION-- typedefs // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup httpclient -/// @{ -//////////////////////////////////////////////////////////////////////////////// - protected: //////////////////////////////////////////////////////////////////////////////// @@ -63,19 +58,10 @@ namespace triagens { enum { READBUFFER_SIZE = 8192 }; -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - // ----------------------------------------------------------------------------- // --SECTION-- constructors / destructors // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup httpclient -/// @{ -//////////////////////////////////////////////////////////////////////////////// - private: GeneralClientConnection (GeneralClientConnection const&); @@ -98,19 +84,10 @@ namespace triagens { virtual ~GeneralClientConnection (); -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - // ----------------------------------------------------------------------------- // --SECTION-- public methods // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup httpclient -/// @{ -//////////////////////////////////////////////////////////////////////////////// - public: //////////////////////////////////////////////////////////////////////////////// @@ -179,19 +156,10 @@ namespace triagens { bool handleRead (double, triagens::basics::StringBuffer&); -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - // ----------------------------------------------------------------------------- // --SECTION-- protected virtual methods // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup httpclient -/// @{ -//////////////////////////////////////////////////////////////////////////////// - protected: //////////////////////////////////////////////////////////////////////////////// @@ -230,19 +198,10 @@ namespace triagens { virtual bool readable () = 0; -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - // ----------------------------------------------------------------------------- // --SECTION-- protected variables // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup httpclient -/// @{ -//////////////////////////////////////////////////////////////////////////////// - protected: //////////////////////////////////////////////////////////////////////////////// @@ -281,10 +240,6 @@ namespace triagens { bool _isConnected; -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - }; } }