зеркало из https://github.com/github/vitess-gh.git
Merge branch 'master' into resharding
This commit is contained in:
Коммит
72b8c89592
|
@ -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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче