Skip to content

Commit

Permalink
support relocate shard
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonardo Menezes committed Dec 25, 2016
1 parent d2281c4 commit e04eec4
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 9 deletions.
11 changes: 11 additions & 0 deletions app/controllers/ClusterOverviewController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,15 @@ class ClusterOverviewController extends BaseController {
}
}

def relocateShard = process { (request, client) =>
val index = request.get("index")
val shard = request.getInt("shard")
val from = request.get("from")
val to = request.get("to")
val server = ElasticServer(request.host, request.authentication)
client.relocateShard(shard, index, from, to, server).map { response =>
Status(response.status)(response.body)
}
}

}
20 changes: 20 additions & 0 deletions app/elastic/ElasticClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,26 @@ trait ElasticClient {
execute(s"${target.host}$path", "GET", None, target.authentication)
}

def relocateShard(shard: Int, index: String, from: String, to: String, target: ElasticServer) = {
val path = "/_cluster/reroute"
val commands =
s"""
|{
| "commands": [
| {
| "move": {
| "shard": $shard,
| "index": \"$index\",
| "from_node": \"$from\",
| "to_node": \"$to\"
| }
| }
| ]
|}
""".stripMargin
execute(s"${target.host}$path", "POST", Some(commands), target.authentication)
}

def getIndexRecovery(index: String, target: ElasticServer) = {
val path = s"/$index/_recovery?active_only=true&human=true"
execute(s"${target.host}$path", "GET", None, target.authentication)
Expand Down
1 change: 1 addition & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ POST /overview/clear_indices_cache @controllers.ClusterOvervi
POST /overview/refresh_indices @controllers.ClusterOverviewController.refreshIndex
POST /overview/delete_indices @controllers.ClusterOverviewController.deleteIndex
POST /overview/get_shard_stats @controllers.ClusterOverviewController.getShardStats
POST /overview/relocate_shard @controllers.ClusterOverviewController.relocateShard

# Navbar module
POST /navbar @controllers.NavbarController.index
Expand Down
2 changes: 2 additions & 0 deletions public/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ table.shard-map { table-layout: fixed; }
.shard-started { border: 2px solid #1AC98E; color: #1AC98E; }
.shard-initializing { border: 1px solid #1CA8DD; color: #1CA8DD; }
.shard-relocated { border: 1px solid #9F85FF; color: #9F85FF; }
.shard-relocating { border: 1px solid #9F85FF; color: #9F85FF; }
.shard-recovering { border: 1px solid #E4D836; color: #E4D836; }
.shard-unassigned { border: 1px solid #8B8F95; color: #8B8F95; }
.shard-spot { border: 1px dashed; opacity: 0.7; padding-top: 2px; }
.shard-replica { border: 1px dashed; opacity: 0.7; }
.row-condensed { margin-right: -.1875rem !important; margin-left: -.1875rem !important; }
.col-condensed { margin-left: 0px; margin-right: 0px; padding-left: 2px !important; padding-right: 2px !important; }
Expand Down
64 changes: 63 additions & 1 deletion public/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,6 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$scope.initializing_shards = 0;
$scope.closed_indices = 0;
$scope.special_indices = 0;
$scope.expandedView = false;
$scope.shardAllocation = true;

$scope.indices_filter = new IndexFilter('', true, false, true, true, 0);
Expand Down Expand Up @@ -940,6 +939,44 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$location.path('index_settings').search('index', index);
};

$scope.select = function(shard) {
$scope.relocatingShard = shard;
};

$scope.relocateShard = function(node) {
var shard = $scope.relocatingShard;
DataService.relocateShard(shard.shard, shard.index, shard.node, node.id,
function(response) {
$scope.relocatingShard = undefined;
RefreshService.refresh();
AlertService.info('Relocation successfully started', response);
},
function(error) {
AlertService.error('Error while starting relocation', error);
}
);
};

$scope.canReceiveShard = function(index, node) {
var shard = $scope.relocatingShard;
if (shard && index) { // in case num indices < num columns
if (shard.node !== node.id && shard.index === index.name) {
var shards = index.shards[node.id];
if (shards) {
var sameShard = function(s) {
return s.shard === shard.shard;
};
if (shards.filter(sameShard).length === 0) {
return true;
}
} else {
return true;
}
}
}
return false;
};

}]);

angular.module('cerebro').controller('RepositoriesController', ['$scope',
Expand Down Expand Up @@ -2069,6 +2106,26 @@ angular.module('cerebro').directive('ngPlainInclude', function() {
};
});

angular.module('cerebro').directive('ngShard', function() {
return {
scope: true,
link: function(scope) {
var shard = scope.shard;
scope.state = shard.state.toLowerCase();
scope.replica = !shard.primary && shard.node;
scope.id = shard.shard + '_' + shard.node + '_' + shard.index;
scope.clazz = scope.replica ? 'shard-replica' : '';
scope.equal = function(other) {
return other && shard.index === other.index &&
shard.node === other.node && shard.shard === other.shard;
};
},
templateUrl: function() {
return 'overview/shard.html';
}
};
});

angular.module('cerebro').factory('AceEditorService', function() {

this.init = function(name) {
Expand Down Expand Up @@ -2349,6 +2406,11 @@ angular.module('cerebro').factory('DataService', ['$rootScope', '$timeout',
request('/overview/get_shard_stats', data, success, error);
};

this.relocateShard = function(shard, index, from, to, success, error) {
var data = {shard: shard, index: index, from: from, to: to};
request('/overview/relocate_shard', data, success, error);
};

// ---------- Create index ----------
this.createIndex = function(index, metadata, success, error) {
var data = {index: index, metadata: metadata};
Expand Down
11 changes: 5 additions & 6 deletions public/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,11 @@
</td>
<td ng-repeat="index in page.elements track by $index">
<span ng-repeat="shard in index.shards[node.id] | orderBy:'shard' track by $index">
<span class="shard shard-{{shard.state.toLowerCase()}} normal-action"
ng-class="{'shard-replica': !shard.primary && shard.node}"
ng-click="shardStats(index.name, node.id, shard.shard)" data-toggle="modal" href="#confirm_dialog"
target="_self">
<small>{{shard.shard}}</small>
</span>
<ng-shard></ng-shard>
</span>
<span class="shard shard-spot normal-action" ng-click="relocateShard(node)"
ng-show="canReceiveShard(index, node)">
<i class="fa fa-download"></i>
</span>
</td>
</tr>
Expand Down
16 changes: 16 additions & 0 deletions public/overview/shard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<span class="dropdown">
<span class="shard shard-{{state}} normal-action {{clazz}}" data-toggle="dropdown" id="{{id}}">
<small>{{shard.shard}}</small>
</span>
<ul class="dropdown-menu" aria-labelledby="{{id}}">
<li ng-click="shardStats(shard.index, shard.node, shard.shard)" data-toggle="modal" href="#confirm_dialog" target="_self">
<a target="_self"><i class="fa fa-fw fa-info-circle"> </i> display shard stats</a>
</li>
<li ng-click="select(shard)" ng-hide="equal(relocatingShard)">
<a target="_self"><i class="fa fa-fw fa-arrows"> </i> select for relocation</a>
</li>
<li ng-click="select()" ng-show="equal(relocatingShard)">
<a target="_self"><i class="fa fa-fw fa-arrows"> </i> unselect for relocation</a>
</li>
</ul>
</span>
39 changes: 38 additions & 1 deletion src/app/components/overview/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$scope.initializing_shards = 0;
$scope.closed_indices = 0;
$scope.special_indices = 0;
$scope.expandedView = false;
$scope.shardAllocation = true;

$scope.indices_filter = new IndexFilter('', true, false, true, true, 0);
Expand Down Expand Up @@ -269,4 +268,42 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$location.path('index_settings').search('index', index);
};

$scope.select = function(shard) {
$scope.relocatingShard = shard;
};

$scope.relocateShard = function(node) {
var shard = $scope.relocatingShard;
DataService.relocateShard(shard.shard, shard.index, shard.node, node.id,
function(response) {
$scope.relocatingShard = undefined;
RefreshService.refresh();
AlertService.info('Relocation successfully started', response);
},
function(error) {
AlertService.error('Error while starting relocation', error);
}
);
};

$scope.canReceiveShard = function(index, node) {
var shard = $scope.relocatingShard;
if (shard && index) { // in case num indices < num columns
if (shard.node !== node.id && shard.index === index.name) {
var shards = index.shards[node.id];
if (shards) {
var sameShard = function(s) {
return s.shard === shard.shard;
};
if (shards.filter(sameShard).length === 0) {
return true;
}
} else {
return true;
}
}
}
return false;
};

}]);
2 changes: 2 additions & 0 deletions src/app/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ table.shard-map { table-layout: fixed; }
.shard-started { border: 2px solid #1AC98E; color: #1AC98E; }
.shard-initializing { border: 1px solid #1CA8DD; color: #1CA8DD; }
.shard-relocated { border: 1px solid #9F85FF; color: #9F85FF; }
.shard-relocating { border: 1px solid #9F85FF; color: #9F85FF; }
.shard-recovering { border: 1px solid #E4D836; color: #E4D836; }
.shard-unassigned { border: 1px solid #8B8F95; color: #8B8F95; }
.shard-spot { border: 1px dashed; opacity: 0.7; padding-top: 2px; }
.shard-replica { border: 1px dashed; opacity: 0.7; }
.row-condensed { margin-right: -.1875rem !important; margin-left: -.1875rem !important; }
.col-condensed { margin-left: 0px; margin-right: 0px; padding-left: 2px !important; padding-right: 2px !important; }
Expand Down
19 changes: 19 additions & 0 deletions src/app/shared/directives/shard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
angular.module('cerebro').directive('ngShard', function() {
return {
scope: true,
link: function(scope) {
var shard = scope.shard;
scope.state = shard.state.toLowerCase();
scope.replica = !shard.primary && shard.node;
scope.id = shard.shard + '_' + shard.node + '_' + shard.index;
scope.clazz = scope.replica ? 'shard-replica' : '';
scope.equal = function(other) {
return other && shard.index === other.index &&
shard.node === other.node && shard.shard === other.shard;
};
},
templateUrl: function() {
return 'overview/shard.html';
}
};
});
5 changes: 5 additions & 0 deletions src/app/shared/services/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ angular.module('cerebro').factory('DataService', ['$rootScope', '$timeout',
request('/overview/get_shard_stats', data, success, error);
};

this.relocateShard = function(shard, index, from, to, success, error) {
var data = {shard: shard, index: index, from: from, to: to};
request('/overview/relocate_shard', data, success, error);
};

// ---------- Create index ----------
this.createIndex = function(index, metadata, success, error) {
var data = {index: index, metadata: metadata};
Expand Down
78 changes: 77 additions & 1 deletion tests/controllers/overview.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ describe('OverviewController', function() {
expect(this.scope.closed_indices).toEqual(0);
expect(this.scope.special_indices).toEqual(0);
expect(this.scope.shardAllocation).toEqual(true);
expect(this.scope.expandedView).toEqual(false);
// index filter
expect(this.scope.indices_filter.name).toEqual('');
expect(this.scope.indices_filter.closed).toEqual(true);
Expand Down Expand Up @@ -186,4 +185,81 @@ describe('OverviewController', function() {

});

describe('relocateShard', function() {
it('relocates selected shard',
function() {
this.scope.relocatingShard = {shard: 1, index: 'i', node: 'n'};
this.DataService.relocateShard = function(s, i, n, n2, success, e) {
success('ok');
};
spyOn(this.DataService, 'relocateShard').andCallThrough();
spyOn(this.AlertService, 'info').andReturn(true);
spyOn(this.RefreshService, 'refresh').andReturn(true);
this.scope.relocateShard({id: 'n2'});
expect(this.DataService.relocateShard).toHaveBeenCalledWith(
1, 'i', 'n', 'n2', jasmine.any(Function), jasmine.any(Function)
);
expect(this.RefreshService.refresh).toHaveBeenCalled();
expect(this.AlertService.info).toHaveBeenCalledWith(
'Relocation successfully started', 'ok'
);
expect(this.scope.relocatingShard).toEqual(undefined);
}
);
it('alerts if relocation fails',
function() {
var shard = {shard: 1, index: 'i', node: 'n'};
this.scope.relocatingShard = shard;
this.DataService.relocateShard = function(s, i, n, n2, s2, error) {
error('ko');
};
spyOn(this.DataService, 'relocateShard').andCallThrough();
spyOn(this.AlertService, 'error').andReturn(true);
this.scope.relocateShard({id: 'n2'});
expect(this.DataService.relocateShard).toHaveBeenCalledWith(
1, 'i', 'n', 'n2', jasmine.any(Function), jasmine.any(Function)
);
expect(this.AlertService.error).toHaveBeenCalledWith(
'Error while starting relocation', 'ko'
);
expect(this.scope.relocatingShard).toEqual(shard);
}
);
});

describe('canReceiveShard', function() {
it('can receive if same index and different node',
function() {
var index = {name: 'i', shards: {n: [{shard: 1}]}};
this.scope.relocatingShard = {shard: 1, index: 'i', node: 'n'};
var receive = this.scope.canReceiveShard(index, {id: 'n2'});
expect(receive).toEqual(true);
}
);
it('cannot receive if same index and different node but containing shard',
function() {
var index = {name: 'i', shards: {n: [{shard: 1}], n2: [{shard: 1}]}};
this.scope.relocatingShard = {shard: 1, index: 'i', node: 'n'};
var receive = this.scope.canReceiveShard(index, {id: 'n2'});
expect(receive).toEqual(false);
}
);
it('cannot receive if same node',
function() {
var index = {name: 'i', shards: {n: [{shard: 1}]}};
this.scope.relocatingShard = {shard: 1, index: 'i2', node: 'n'};
var receive = this.scope.canReceiveShard(index, {id: 'n'});
expect(receive).toEqual(false);
}
);
it('cannot receive if different index',
function() {
var index = {name: 'i2', shards: {n: [{shard: 1}]}};
this.scope.relocatingShard = {shard: 1, index: 'i', node: 'n'};
var receive = this.scope.canReceiveShard(index, {id: 'n2'});
expect(receive).toEqual(false);
}
);
});

});

0 comments on commit e04eec4

Please sign in to comment.