Skip to content

Commit

Permalink
core: abort all of a kind; CLI 'ais stop'; strings: Damerau-Levensthein
Browse files Browse the repository at this point in the history
* fix do-abort() when given only a 'kind'
* CLI: rewrite 'ais stop', add inline help, add confirmation
* move some of the strings utils to CLI (where they belong)
* add yet another canonical name: `xact.Cname`

Signed-off-by: Alex Aizman <[email protected]>
  • Loading branch information
alex-aizman committed Dec 21, 2023
1 parent 3db864b commit 1acfbc5
Show file tree
Hide file tree
Showing 19 changed files with 204 additions and 194 deletions.
4 changes: 2 additions & 2 deletions ais/prxtxn.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ func (p *proxy) tcobjs(bckFrom, bckTo *meta.Bck, config *cmn.Config, msg *apc.Ac
}

if xid == "" {
xid = strings.Join(all, xact.UUIDSepa) // return comma-separated x-tco IDs
xid = strings.Join(all, xact.SepaID) // return comma-separated x-tco IDs
}
return xid, nil
}
Expand Down Expand Up @@ -807,7 +807,7 @@ func (p *proxy) createArchMultiObj(bckFrom, bckTo *meta.Bck, msg *apc.ActMsg) (x
if err != nil || xid != "" {
return
}
return strings.Join(all, xact.UUIDSepa), nil
return strings.Join(all, xact.SepaID), nil
}

func (p *proxy) beginRmTarget(si *meta.Snode, msg *apc.ActMsg) error {
Expand Down
2 changes: 1 addition & 1 deletion ais/test/bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2475,7 +2475,7 @@ func TestCopyBucket(t *testing.T) {
}
tassert.CheckFatal(t, err)
tlog.Logf("copying %s => %s: %s\n", srcm.bck, dstm.bck, uuid)
if uuids := strings.Split(uuid, xact.UUIDSepa); len(uuids) > 1 {
if uuids := strings.Split(uuid, xact.SepaID); len(uuids) > 1 {
for _, u := range uuids {
tassert.Fatalf(t, xact.IsValidUUID(u), "invalid UUID %q", u)
}
Expand Down
2 changes: 1 addition & 1 deletion api/xaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func AbortXaction(bp BaseParams, args *xact.ArgsMsg) (err error) {
// querying and waiting
//

// returns unique ':'-separated kind/ID pairs (strings)
// returns a slice of canonical xaction names, as in: `xact.Cname()`
// e.g.: put-copies[D-ViE6HEL_j] list[H96Y7bhR2s] copy-bck[matRQMRes] put-copies[pOibtHExY]
// TODO: return idle xactions separately
func GetAllRunningXactions(bp BaseParams, kindOrName string) (out []string, err error) {
Expand Down
1 change: 1 addition & 0 deletions cluster/xaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type (
// reporting: log, err
String() string
Name() string
Cname() string

// modifiers
Finish()
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/cli/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ const nodeLogFlushName = "log.flush_time"

const (
tabtab = "press <TAB-TAB> to select"
tabHelpOpt = "press <TAB-TAB> to select, '--help' for options"
tabHelpOpt = "press <TAB-TAB> to select, '--help' for more options"
tabHelpDet = "press <TAB-TAB> to select, '--help' for details"
)

Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/cli/err.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func newAdditionalInfoError(err error, info string) error {
}

func (e *errAdditionalInfo) Error() string {
return fmt.Sprintf("%s.\n%s\n", e.baseErr.Error(), cos.StrToSentence(e.additionalInfo))
return fmt.Sprintf("%s.\n%s\n", e.baseErr.Error(), strToSentence(e.additionalInfo))
}

/////////////////////
Expand Down
110 changes: 56 additions & 54 deletions cmd/cli/cli/job_hdlr.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,16 @@ var (
stopCmdsFlags = []cli.Flag{
allRunningJobsFlag,
regexJobsFlag,
yesFlag,
}
jobStopSub = cli.Command{
Name: commandStop,
Usage: "terminate a single batch job or multiple jobs (" + tabHelpOpt + ")",
Name: commandStop,
Usage: "terminate a single batch job or multiple jobs, e.g.:\n" +
indent1 + "\t- 'stop tco-cysbohAGL'\t- terminate a given job identified by its unique ID;\n" +
indent1 + "\t- 'stop copy-listrange'\t- terminate all multi-object copies;\n" +
indent1 + "\t- 'stop copy-objects'\t- same as above (using display name);\n" +
indent1 + "\t- 'stop --all'\t- terminate all running jobs\n" +
indent1 + strToSentence(tabHelpOpt),
ArgsUsage: jobAnyArg,
Flags: stopCmdsFlags,
Action: stopJobHandler,
Expand Down Expand Up @@ -660,7 +666,7 @@ func stopJobHandler(c *cli.Context) error {
if err != nil {
return err
}
if name == "" && xid == "" {
if name == "" && xid == "" && !flagIsSet(c, allRunningJobsFlag) {
return missingArgumentsError(c, c.Command.ArgsUsage)
}
if daemonID != "" {
Expand Down Expand Up @@ -691,27 +697,41 @@ func stopJobHandler(c *cli.Context) error {
}
}

var otherID string
var (
otherID string
xactID = xid
xname, xactKind string
)
if name == "" && xid != "" {
name, otherID = xid2Name(xid)
}
if name != "" {
xactKind, xname = xact.GetKindName(name)
if xactKind == "" {
return incorrectUsageMsg(c, "unrecognized or misplaced option '%s'", name)
}
}

// confirm unless
if xactID == "" && name != "" {
if name != commandRebalance && !flagIsSet(c, yesFlag) && !flagIsSet(c, allRunningJobsFlag) {
prompt := fmt.Sprintf("Stop all '%s' jobs?", name)
if ok := confirm(c, prompt); !ok {
return nil
}
}
}

// specialized stop
switch name {
case cmdDownload:
if xid == "" {
if flagIsSet(c, allRunningJobsFlag) || regex != "" {
return stopDownloadRegex(c, regex)
}
return missingArgumentsError(c, jobIDArgument)
return stopDownloadRegex(c, regex)
}
return stopDownloadHandler(c, xid)
case cmdDsort:
if xid == "" {
if flagIsSet(c, allRunningJobsFlag) || regex != "" {
return stopDsortRegex(c, regex)
}
return missingArgumentsError(c, jobIDArgument)
return stopDsortRegex(c, regex)
}
return stopDsortHandler(c, xid)
case commandETL:
Expand All @@ -720,33 +740,10 @@ func stopJobHandler(c *cli.Context) error {
return stopClusterRebalanceHandler(c)
}

// common stop
var (
xactID = xid
xname, xactKind string
)
if name != "" {
xactKind, xname = xact.GetKindName(name)
if xactKind == "" {
return incorrectUsageMsg(c, "unrecognized or misplaced option '%s'", name)
}
}
// generic xstop

if xactID == "" {
// NOTE: differentiate global (cluster-wide) xactions from non-global
// (the latter require --all to confirm stopping potentially many...)
var isGlobal bool
if _, dtor, err := xact.GetDescriptor(xactKind); err == nil {
isGlobal = dtor.Scope == xact.ScopeG || dtor.Scope == xact.ScopeGB
}
if !isGlobal && !flagIsSet(c, allRunningJobsFlag) {
msg := fmt.Sprintf("Expecting either %s argument or %s option (with or without regular expression)",
jobIDArgument, qflprn(allRunningJobsFlag))
return cannotExecuteError(c, errors.New("missing "+jobIDArgument), msg)
}

// stop all xactions of a given kind (TODO: bck)
return stopXactionKind(c, xactKind, xname, bck)
return stopXactionKindOrAll(c, xactKind, xname, bck)
}

// query
Expand Down Expand Up @@ -790,27 +787,32 @@ func stopJobHandler(c *cli.Context) error {
return nil
}

// TODO: `bck` must provide additional filtering (NIY)
func stopXactionKind(c *cli.Context, xactKind, xname string, bck cmn.Bck) error {
xactIDs, err := api.GetAllRunningXactions(apiBP, xactKind)
// NOTE: the '--all' case when both (xactKind == "" && xname == "") - is also handled here
// TODO: aistore supports `bck` for additional filtering (NIY)
func stopXactionKindOrAll(c *cli.Context, xactKind, xname string, bck cmn.Bck) error {
cnames, err := api.GetAllRunningXactions(apiBP, xactKind)
if err != nil {
return V(err)
}
if len(xactIDs) == 0 {
actionDone(c, fmt.Sprintf("No running '%s' jobs, nothing to do", xname))
if len(cnames) == 0 {
var what string
if xname != "" {
what = " '" + xname + "'"
}
actionDone(c, fmt.Sprintf("No running%s jobs, nothing to do", what))
return nil
}
for _, ki := range xactIDs {
var (
i = strings.IndexByte(ki, xact.LeftID[0])
xactID = ki[i+1 : len(ki)-1] // extract UUID from "name[UUID]"
args = xact.ArgsMsg{ID: xactID, Kind: xactKind, Bck: bck}
msg = formatXactMsg(xactID, xname, bck)
)
for _, cname := range cnames {
xactKind, xactID, err := xact.ParseCname(cname)
if err != nil {
debug.AssertNoErr(err)
continue
}
args := xact.ArgsMsg{ID: xactID, Kind: xactKind, Bck: bck}
if err := api.AbortXaction(apiBP, &args); err != nil {
actionWarn(c, fmt.Sprintf("failed to stop %s: %v", msg, err))
actionWarn(c, fmt.Sprintf("failed to stop %s: %v", cname, err))
} else {
actionDone(c, "Stopped "+msg)
actionDone(c, "Stopped "+cname)
}
}
return nil
Expand All @@ -823,13 +825,13 @@ func formatXactMsg(xactID, xactKind string, bck cmn.Bck) string {
}
switch {
case xactKind != "" && xactID != "":
return fmt.Sprintf("%s[%s%s]", xactKind, xactID, sb)
return fmt.Sprintf("%s[%s]%s", xactKind, xactID, sb)
case xactKind != "" && sb != "":
return fmt.Sprintf("%s[%s]", xactKind, sb)
return fmt.Sprintf("%s%s", xactKind, sb)
case xactKind != "":
return xactKind
default:
return fmt.Sprintf("job[%s%s]", xactID, sb)
return fmt.Sprintf("[%s]%s", xactID, sb)
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/cli/multiobj.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func _evictOne(c *cli.Context, shift int) error {
}
if !bck.IsRemote() {
const msg = "evicting objects from AIS buckets (ie., buckets with no remote backends) is not allowed."
return errors.New(msg + "\n(Tip: maybe use 'ais object rm' instead)")
return errors.New(msg + "\n(Tip: consider 'ais object rm' or 'ais rmb', see --help for details)")
}
if _, err := headBucket(bck, false /* don't add */); err != nil {
return err
Expand Down
61 changes: 61 additions & 0 deletions cmd/cli/cli/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Package cli provides easy-to-use commands to manage, monitor, and utilize AIS clusters.
/*
* Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
*/
package cli

import (
"strings"

"github.com/NVIDIA/aistore/cmn/cos"
)

// based on https://en.wikipedia.org/wiki/Damerau–Levenshtein_distance
func DamerauLevenstheinDistance(s, t string) int {
distances := make([][]int, len(s)+1)
for i := range distances {
distances[i] = make([]int, len(t)+1)
}

// cost of dropping all characters is equal to a length
for i := range distances {
distances[i][0] = i
}
for j := range distances[0] {
distances[0][j] = j
}

for j := 1; j <= len(t); j++ {
for i := 1; i <= len(s); i++ {
if s[i-1] == t[j-1] {
distances[i][j] = distances[i-1][j-1]
continue
}

// characters are different. Take character from s or from t or drop character at all
distance := min(distances[i-1][j], distances[i][j-1], distances[i-1][j-1])

// check if error might be swap of subsequent characters
if i >= 2 && j >= 2 && s[i-2] == t[j-1] && s[i-1] == t[j-2] {
distance = min(distance, distances[i-2][j-2])
}
distances[i][j] = distance + 1
}
}
return distances[len(s)][len(t)]
}

func strToSentence(str string) string {
if str == "" {
return ""
}
capitalized := capitalizeFirst(str)
if !cos.IsLastB(capitalized, '.') {
capitalized += "."
}
return capitalized
}

func capitalizeFirst(s string) string {
return strings.ToUpper(s[:1]) + s[1:]
}
2 changes: 1 addition & 1 deletion cmd/cli/cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func findClosestCommand(cmd string, candidates []cli.Command) (result string, di
closestName string
)
for i := 0; i < len(candidates); i++ {
dist := cos.DamerauLevenstheinDistance(cmd, candidates[i].Name)
dist := DamerauLevenstheinDistance(cmd, candidates[i].Name)
if dist < minDist {
minDist = dist
closestName = candidates[i].Name
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.21

// direct
require (
github.com/NVIDIA/aistore v1.3.22-0.20231213235703-bec1dd514194
github.com/NVIDIA/aistore v1.3.22-0.20231221174113-ee345a2731e7
github.com/fatih/color v1.16.0
github.com/json-iterator/go v1.1.12
github.com/onsi/ginkgo v1.16.5
Expand Down
4 changes: 2 additions & 2 deletions cmd/cli/go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/NVIDIA/aistore v1.3.22-0.20231213235703-bec1dd514194 h1:MhRmI/9jw6Y/eOuDt2OVJ/L4zAgx3//wBU4cXx4mV94=
github.com/NVIDIA/aistore v1.3.22-0.20231213235703-bec1dd514194/go.mod h1:cOTgDt5fVCQOB+rnvYZgVFRF3dEzPqu8f22F3F+Yvtg=
github.com/NVIDIA/aistore v1.3.22-0.20231221174113-ee345a2731e7 h1:nZkbX2fnwoIkVixj+PCJ0ZCf2AyApHfF3iuOwxDo75A=
github.com/NVIDIA/aistore v1.3.22-0.20231221174113-ee345a2731e7/go.mod h1:jpWmGuqxnY+akx81S5eqHhGdgSENm0mVYRrVbpCW4/I=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
Expand Down
28 changes: 28 additions & 0 deletions cmd/cli/test/strings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Package test provides tests for common low-level types and utilities for all aistore projects
/*
* Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
*/
package test_test

import (
"testing"

"github.com/NVIDIA/aistore/cmd/cli/cli"
"github.com/NVIDIA/aistore/tools/tassert"
)

func TestWordsDistance(t *testing.T) {
testCases := []struct{ expected, actual int }{
{0, cli.DamerauLevenstheinDistance("test", "test")},
{1, cli.DamerauLevenstheinDistance("tests", "test")},
{2, cli.DamerauLevenstheinDistance("cp", "copy")},
{1, cli.DamerauLevenstheinDistance("teet", "test")},
{1, cli.DamerauLevenstheinDistance("test", "tset")},
{3, cli.DamerauLevenstheinDistance("kitten", "sitting")},
}

for _, tc := range testCases {
tassert.Errorf(t, tc.expected == tc.actual,
"expected Damerau–Levenshtein distance %d, got %d", tc.expected, tc.actual)
}
}
23 changes: 0 additions & 23 deletions cmn/cos/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,6 @@ func ParseBool(s string) (bool, error) {
return strconv.ParseBool(s)
}

func StringSliceToIntSlice(strs []string) ([]int64, error) {
res := make([]int64, 0, len(strs))
for _, s := range strs {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return nil, err
}
res = append(res, i)
}
return res, nil
}

func StrToSentence(str string) string {
if str == "" {
return ""
}
capitalized := CapitalizeString(str)
if !IsLastB(capitalized, '.') {
capitalized += "."
}
return capitalized
}

func ConvertToString(value any) (valstr string, err error) {
switch v := value.(type) {
case string:
Expand Down
Loading

0 comments on commit 1acfbc5

Please sign in to comment.