Merge branch 'master' into resharding

This commit is contained in:
Alain Jobart 2015-07-16 08:36:59 -07:00
Родитель 42d9f7fb18 7f4856f82e
Коммит 72b8c89592
7 изменённых файлов: 248 добавлений и 37 удалений

Просмотреть файл

@ -16,7 +16,7 @@ import (
const apiPrefix = "/api/"
func handleGet(collection string, getFunc func(*http.Request) (interface{}, error)) {
func handleCollection(collection string, getFunc func(*http.Request) (interface{}, error)) {
http.HandleFunc(apiPrefix+collection+"/", func(w http.ResponseWriter, r *http.Request) {
// Get the requested object.
obj, err := getFunc(r)
@ -60,46 +60,79 @@ func getItemPath(url string) string {
return parts[1]
}
func initAPI(ctx context.Context, ts topo.Server) {
func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository) {
tabletHealthCache := newTabletHealthCache(ts)
// Get Cells
handleGet("cells", func(r *http.Request) (interface{}, error) {
// Cells
handleCollection("cells", func(r *http.Request) (interface{}, error) {
if getItemPath(r.URL.Path) != "" {
return nil, errors.New("cells can only be listed, not retrieved")
}
return ts.GetKnownCells(ctx)
})
// Get Keyspaces
handleGet("keyspaces", func(r *http.Request) (interface{}, error) {
// Keyspaces
handleCollection("keyspaces", func(r *http.Request) (interface{}, error) {
keyspace := getItemPath(r.URL.Path)
// List all keyspaces.
if keyspace == "" {
return ts.GetKeyspaces(ctx)
}
// Perform an action on a keyspace.
if r.Method == "POST" {
if err := r.ParseForm(); err != nil {
return nil, err
}
action := r.FormValue("action")
if action == "" {
return nil, errors.New("must specify action")
}
return actions.ApplyKeyspaceAction(ctx, action, keyspace, r), nil
}
// Get the keyspace record.
return ts.GetKeyspace(ctx, keyspace)
})
// Get Shards
handleGet("shards", func(r *http.Request) (interface{}, error) {
// Shards
handleCollection("shards", func(r *http.Request) (interface{}, error) {
shardPath := getItemPath(r.URL.Path)
if !strings.Contains(shardPath, "/") {
return nil, fmt.Errorf("invalid shard path: %q", shardPath)
}
parts := strings.SplitN(shardPath, "/", 2)
if parts[1] == "" {
// It's just a keyspace. List the shards.
return ts.GetShardNames(ctx, parts[0])
keyspace := parts[0]
shard := parts[1]
// List the shards in a keyspace.
if shard == "" {
return ts.GetShardNames(ctx, keyspace)
}
// It's a keyspace/shard reference.
return ts.GetShard(ctx, parts[0], parts[1])
// Perform an action on a shard.
if r.Method == "POST" {
if err := r.ParseForm(); err != nil {
return nil, err
}
action := r.FormValue("action")
if action == "" {
return nil, errors.New("must specify action")
}
return actions.ApplyShardAction(ctx, action, keyspace, shard, r), nil
}
// Get the shard record.
return ts.GetShard(ctx, keyspace, shard)
})
// Get Tablets
handleGet("tablets", func(r *http.Request) (interface{}, error) {
// Tablets
handleCollection("tablets", func(r *http.Request) (interface{}, error) {
tabletPath := getItemPath(r.URL.Path)
// List tablets based on query params.
if tabletPath == "" {
// List tablets based on query params.
if err := r.ParseForm(); err != nil {
return nil, err
}
@ -125,7 +158,7 @@ func initAPI(ctx context.Context, ts topo.Server) {
return ts.GetTabletsByCell(ctx, cell)
}
// Tablet Health
// Get tablet health.
if parts := strings.Split(tabletPath, "/"); len(parts) == 2 && parts[1] == "health" {
tabletAlias, err := topo.ParseTabletAliasString(parts[0])
if err != nil {
@ -134,16 +167,29 @@ func initAPI(ctx context.Context, ts topo.Server) {
return tabletHealthCache.Get(ctx, tabletAlias)
}
// Get a specific tablet.
tabletAlias, err := topo.ParseTabletAliasString(tabletPath)
if err != nil {
return nil, err
}
// Perform an action on a tablet.
if r.Method == "POST" {
if err := r.ParseForm(); err != nil {
return nil, err
}
action := r.FormValue("action")
if action == "" {
return nil, errors.New("must specify action")
}
return actions.ApplyTabletAction(ctx, action, tabletAlias, r), nil
}
// Get the tablet record.
return ts.GetTablet(ctx, tabletAlias)
})
// Get EndPoints
handleGet("endpoints", func(r *http.Request) (interface{}, error) {
// EndPoints
handleCollection("endpoints", func(r *http.Request) (interface{}, error) {
// We expect cell/keyspace/shard/tabletType.
epPath := getItemPath(r.URL.Path)
parts := strings.Split(epPath, "/")

Просмотреть файл

@ -126,6 +126,15 @@ func main() {
return "", wr.TabletManagerClient().Ping(ctx, ti)
})
actionRepo.RegisterTabletAction("RefreshState", acl.ADMIN,
func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) {
ti, err := wr.TopoServer().GetTablet(ctx, tabletAlias)
if err != nil {
return "", err
}
return "", wr.TabletManagerClient().RefreshState(ctx, ti)
})
actionRepo.RegisterTabletAction("ScrapTablet", acl.ADMIN,
func(ctx context.Context, wr *wrangler.Wrangler, tabletAlias topo.TabletAlias, r *http.Request) (string, error) {
// refuse to scrap tablets that are not spare
@ -310,7 +319,7 @@ func main() {
}
// Serve the REST API for the vtctld web app.
initAPI(context.Background(), ts)
initAPI(context.Background(), ts, actionRepo)
// vschema viewer
http.HandleFunc("/vschema", func(w http.ResponseWriter, r *http.Request) {
@ -320,7 +329,7 @@ func main() {
}
schemafier, ok := ts.(topo.Schemafier)
if !ok {
httpErrorf(w, r, "%s", fmt.Errorf("%T doesn's support schemafier API", ts))
httpErrorf(w, r, "%s", fmt.Errorf("%T doesn't support schemafier API", ts))
}
var data struct {
Error error

Просмотреть файл

@ -404,7 +404,13 @@ class Tablet(object):
if not self.proc:
Tablet.tablets_running += 1
self.proc = utils.run_bg(args, stderr=stderr_fd, extra_env=extra_env)
log_message = "Started vttablet: %s (%s) with pid: %s - Log files: %s/vttablet.*.{INFO,WARNING,ERROR,FATAL}.*.%s" % \
(self.tablet_uid, self.tablet_alias, self.proc.pid, environment.vtlogroot, self.proc.pid)
# This may race with the stderr output from the process (though that's usually empty).
stderr_fd.write(log_message + '\n')
stderr_fd.close()
logging.debug(log_message)
# wait for query service to be in the right state
if wait_for_state:

Просмотреть файл

@ -0,0 +1,33 @@
<md-dialog aria-label="Action">
<form>
<md-toolbar>
<div class="md-toolbar-tools">
<h2 ng-bind="action.title"></h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="hide()">
<md-icon md-svg-src="close", aria-label="Close dialog"></md-icon>
</md-button>
</div>
</md-toolbar>
<md-dialog-content layout="column">
<div ng-if="!result.$resolved" flex layout="column" layout-align="center center">
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
<h3>Executing action...</h3>
</div>
<md-content ng-if="result.$resolved" class="md-padding">
<h2 ng-bind="result.Error === undefined || result.Error ? 'Action Failed' : 'Action Succeeded'"></h2>
<p ng-bind="result.Output"></p>
</md-content>
</md-dialog-content>
<div class="md-actions" layout="row" layout-align="end center">
<md-button ng-click="hide()" class="md-primary">Close</md-button>
</div>
</form>
</md-dialog>

Просмотреть файл

@ -89,14 +89,16 @@ app.controller('AppCtrl', function($scope, $mdSidenav, $route, $location,
};
});
app.controller('KeyspacesCtrl', function($scope, $mdDialog, keyspaces, shards) {
app.controller('KeyspacesCtrl', function($scope, keyspaces, shards, actions) {
$scope.keyspaceActions = [
'Validate Keyspace',
'Validate Permissions',
'Validate Schema',
'Validate Version'
{name: 'ValidateKeyspace', title: 'Validate Keyspace'},
{name: 'ValidateSchemaKeyspace', title: 'Validate Schema'},
{name: 'ValidateVersionKeyspace', title: 'Validate Version'},
{name: 'ValidatePermissionsKeyspace', title: 'Validate Permissions'},
];
$scope.actions = actions;
$scope.refreshData = function() {
// Get list of keyspace names.
keyspaces.query(function(ksnames) {
@ -114,15 +116,30 @@ app.controller('KeyspacesCtrl', function($scope, $mdDialog, keyspaces, shards) {
});
app.controller('ShardCtrl', function($scope, $routeParams, $timeout,
shards, tablets, tabletinfo) {
shards, tablets, tabletinfo, actions) {
var keyspace = $routeParams.keyspace;
var shard = $routeParams.shard;
$scope.keyspace = {name: keyspace};
$scope.shard = {name: shard};
$scope.actions = actions;
$scope.shardActions = [
{name: 'ValidateShard', title: 'Validate Shard'},
{name: 'ValidateSchemaShard', title: 'Validate Schema'},
{name: 'ValidateVersionShard', title: 'Validate Version'},
{name: 'ValidatePermissionsShard', title: 'Validate Permissions'},
];
$scope.tabletActions = [
'Ping', 'Scrap', 'Delete', 'Reload Schema'
{name: 'Ping', title: 'Ping'},
{name: 'RefreshState', title: 'Refresh State', confirm: 'This will tell the tablet to re-read its topology record and adjust its state accordingly.'},
{name: 'ReloadSchema', title: 'Reload Schema', confirm: 'This will tell the tablet to refresh its schema cache by querying mysqld.'},
{name: 'ScrapTablet', title: 'Scrap', confirm: 'This will tell the tablet to remove itself from serving.'},
{name: 'ScrapTabletForce', title: 'Scrap (force)', confirm: 'This will externally remove the tablet from serving, without telling the tablet.'},
{name: 'DeleteTablet', title: 'Delete', confirm: 'This will delete the tablet record from topology.'},
];
$scope.tabletType = function(tablet) {
@ -240,20 +257,101 @@ app.controller('TopoCtrl', function($scope, $routeParams) {
$scope.path = $routeParams.path;
});
app.factory('actions', function($mdDialog, keyspaces, shards, tablets) {
var svc = {};
function actionDialogController($scope, $mdDialog, action, result) {
$scope.hide = function() { $mdDialog.hide(); };
$scope.action = action;
$scope.result = result;
}
function showResult(ev, action, result) {
$mdDialog.show({
controller: actionDialogController,
templateUrl: 'action-dialog.html',
parent: angular.element(document.body),
targetEvent: ev,
locals: {
action: action,
result: result
}
});
}
function confirm(ev, action, doIt) {
if (action.confirm) {
var dialog = $mdDialog.confirm()
.parent(angular.element(document.body))
.title('Confirm ' + action.title)
.content(action.confirm)
.ariaLabel('Confirm action')
.ok('OK')
.cancel('Cancel')
.targetEvent(ev);
$mdDialog.show(dialog).then(doIt);
} else {
doIt();
}
}
svc.applyKeyspace = function(ev, action, keyspace) {
confirm(ev, action, function() {
var result = keyspaces.action({
keyspace: keyspace, action: action.name
}, '');
showResult(ev, action, result);
});
};
svc.applyShard = function(ev, action, keyspace, shard) {
confirm(ev, action, function() {
var result = shards.action({
keyspace: keyspace,
shard: shard,
action: action.name
}, '');
showResult(ev, action, result);
});
};
svc.applyTablet = function(ev, action, tabletAlias) {
confirm(ev, action, function() {
var result = tablets.action({
tablet: tabletAlias.Cell+'-'+tabletAlias.Uid,
action: action.name
}, '');
showResult(ev, action, result);
});
};
svc.label = function(action) {
return action.confirm ? action.title + '...' : action.title;
};
return svc;
});
app.factory('cells', function($resource) {
return $resource('/api/cells/');
});
app.factory('keyspaces', function($resource) {
return $resource('/api/keyspaces/:keyspace');
return $resource('/api/keyspaces/:keyspace', {}, {
'action': {method: 'POST'}
});
});
app.factory('shards', function($resource) {
return $resource('/api/shards/:keyspace/:shard');
return $resource('/api/shards/:keyspace/:shard', {}, {
'action': {method: 'POST'}
});
});
app.factory('tablets', function($resource) {
return $resource('/api/tablets/:tablet');
return $resource('/api/tablets/:tablet', {}, {
'action': {method: 'POST'}
});
});
app.factory('tabletinfo', function($resource) {

Просмотреть файл

@ -15,11 +15,13 @@
<h2>{{keyspace.name}}</h2>
<span flex></span>
<md-menu>
<md-button class="md-icon-button" aria-label="Actions" ng-click="$mdOpenMenu()" md-menu-origin>
<md-button class="md-icon-button" aria-label="Keyspace actions" ng-click="$mdOpenMenu()" md-menu-origin>
<md-icon md-svg-icon="more_vert"></md-icon>
</md-button>
<md-menu-content>
<md-menu-item ng-repeat="action in keyspaceActions"><md-button>{{action}}</md-button></md-menu-item>
<md-menu-item ng-repeat="action in keyspaceActions">
<md-button ng-click="actions.applyKeyspace($event,action,keyspace.name)">{{actions.label(action)}}</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</div>

Просмотреть файл

@ -1,7 +1,22 @@
<md-content class="md-padding">
<md-card>
<md-toolbar><div class="md-toolbar-tools"><h2>Shard Record</h2></div></md-toolbar>
<md-toolbar>
<div class="md-toolbar-tools">
<h2>Shard Record</h2>
<span flex></span>
<md-menu>
<md-button class="md-icon-button" aria-label="Shard actions" ng-click="$mdOpenMenu()" md-menu-origin>
<md-icon md-svg-icon="more_vert"></md-icon>
</md-button>
<md-menu-content>
<md-menu-item ng-repeat="action in shardActions">
<md-button ng-click="actions.applyShard($event, action, keyspace.name, shard.name)">{{actions.label(action)}}</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</div>
</md-toolbar>
<md-card-content>
<div layout="column">
@ -44,11 +59,13 @@
<h3>{{tablet.Alias.Cell}}-{{tablet.Alias.Uid}} [{{tabletType(tablet)}}]</h3>
<span flex></span>
<md-menu>
<md-button class="md-icon-button" aria-label="Actions" ng-click="$mdOpenMenu()" md-menu-origin>
<md-button class="md-icon-button" aria-label="Tablet actions" ng-click="$mdOpenMenu()" md-menu-origin>
<md-icon md-svg-icon="more_vert"></md-icon>
</md-button>
<md-menu-content>
<md-menu-item ng-repeat="action in tabletActions"><md-button>{{action}}</md-button></md-menu-item>
<md-menu-item ng-repeat="action in tabletActions">
<md-button ng-click="actions.applyTablet($event, action, tablet.Alias)">{{actions.label(action)}}</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</div>