From 5e802f8ed70ac67f0fdaa3fab9649bc6ebae3bc3 Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Wed, 28 Jul 2021 16:09:58 +0800 Subject: [PATCH 01/16] enhancements: human-readable error message and hints, file system location(r/w access) support, container-dir to store dump and local-dir to save dump copy --- cf_cli_java_plugin.go | 60 +++++++++--- go.mod | 27 +++++ go.sum | 183 ++++++++++++++++++++++++++++++++++ utils/.DS_Store | Bin 0 -> 6148 bytes utils/cfutils.go | 222 ++++++++++++++++++++++++++++++++++++++++++ utils/go.mod | 5 + utils/go.sum | 2 + 7 files changed, 487 insertions(+), 12 deletions(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 utils/.DS_Store create mode 100644 utils/cfutils.go create mode 100644 utils/go.mod create mode 100644 utils/go.sum diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index 786b285..d71d5f7 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -20,6 +20,8 @@ import ( "code.cloudfoundry.org/cli/cf/trace" "code.cloudfoundry.org/cli/plugin" + "utils" + guuid "github.com/satori/go.uuid" "github.com/simonleung8/flags" ) @@ -125,7 +127,9 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator commandFlags.NewIntFlagWithDefault("app-instance-index", "i", "application `instance` to connect to", -1) commandFlags.NewBoolFlag("keep", "k", "whether to `keep` the heap/thread-dump on the container of the application instance after having downloaded it locally") commandFlags.NewBoolFlag("dry-run", "n", "triggers the `dry-run` mode to show only the cf-ssh command that would have been executed") - + commandFlags.NewStringFlag("container-dir", "cd", "specify folder path where the dump file should be stored in the container") + commandFlags.NewStringFlag("local-dir", "ld", "specify the folder where the dump file will be downloaded") + parseErr := commandFlags.Parse(args[1:]...) if parseErr != nil { return "", &InvalidUsageError{message: fmt.Sprintf("Error while parsing command arguments: %v", parseErr)} @@ -134,6 +138,10 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator applicationInstance := commandFlags.Int("app-instance-index") keepAfterDownload := commandFlags.IsSet("keep") + remoteDir := commandFlags.String("container-dir") + localDir := commandFlags.String("local-dir") + + arguments := commandFlags.Args() argumentLen := len(arguments) @@ -168,10 +176,23 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator } var remoteCommandTokens = []string{JavaDetectionCommand} - + remoteFileFullPath := "" + localFileFullPath:= "" switch command { case heapDumpCommand: - heapdumpFileName := "/tmp/heapdump-" + uuidGenerator.Generate() + ".hprof" + + valid, _ :=utils.CheckRequiredTools(applicationName) + if !valid{ + return "", nil + } + + fspath, _ := utils.GetAvailablePath(applicationName, remoteDir) + //fmt.Println(fspath) + dumpFile := applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" + heapdumpFileName := fspath + "/" + dumpFile + remoteFileFullPath = fspath + "/" + dumpFile + localFileFullPath = localDir + "/" + dumpFile + remoteCommandTokens = append(remoteCommandTokens, // Check file does not already exist "if [ -f "+heapdumpFileName+" ]; then echo >&2 'Heap dump "+heapdumpFileName+" already exists'; exit 1; fi", @@ -189,26 +210,28 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator "OUTPUT=$( ${JMAP_COMMAND} -dump:format=b,file="+heapdumpFileName+" $(pidof java) ) || STATUS_CODE=$?", "if [ ! -s "+heapdumpFileName+" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", - "cat "+heapdumpFileName, - "exit 0", - "fi", + //"echo "+heapdumpFileName, + //"exit 0", + //"fi", // SAP JVM: Wrap everything in an if statement in case jvmmon is available "JVMMON_COMMAND=`find -executable -name jvmmon | head -1 | tr -d [:space:]`", - "if [ -n \"${JVMMON_COMMAND}\" ]; then true", + "elif [ -n \"${JVMMON_COMMAND}\" ]; then true", "OUTPUT=$( ${JVMMON_COMMAND} -pid $(pidof java) -c \"dump heap\" ) || STATUS_CODE=$?", "sleep 5", // Writing the heap dump is triggered asynchronously -> give the jvm some time to create the file "HEAP_DUMP_NAME=`find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1`", "SIZE=-1; OLD_SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); while [ \"${SIZE}\" != \"${OLD_SIZE}\" ]; do sleep 3; SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); done", "if [ ! -s \"${HEAP_DUMP_NAME}\" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", - "cat ${HEAP_DUMP_NAME}", + //"echo ${HEAP_DUMP_NAME}", + //"fi", "fi") - if !keepAfterDownload { + //if !keepAfterDownload { + // fmt.Println("delete after download") // OpenJDK - remoteCommandTokens = append(remoteCommandTokens, "rm -f "+heapdumpFileName) + //remoteCommandTokens = append(remoteCommandTokens, "rm -f "+heapdumpFileName) // SAP JVM - remoteCommandTokens = append(remoteCommandTokens, "if [ -n \"${HEAP_DUMP_NAME}\" ]; then rm -f ${HEAP_DUMP_NAME} ${HEAP_DUMP_NAME%.*}.addons; fi") - } + //remoteCommandTokens = append(remoteCommandTokens, "if [ -n \"${HEAP_DUMP_NAME}\" ]; then rm -f ${HEAP_DUMP_NAME} ${HEAP_DUMP_NAME%.*}.addons; fi") + //} case threadDumpCommand: // OpenJDK remoteCommandTokens = append(remoteCommandTokens, "JSTACK_COMMAND=`find -executable -name jstack | head -1`; if [ -n \"${JSTACK_COMMAND}\" ]; then ${JSTACK_COMMAND} $(pidof java); exit 0; fi") @@ -229,7 +252,20 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator cfSSHArguments = append(cfSSHArguments, remoteCommand) output, err := commandExecutor.Execute(cfSSHArguments) + + if strings.Compare(command, heapDumpCommand) == 0{ + + finalFile, err := utils.FindDumpFile(applicationName, remoteFileFullPath) + if err != nil && finalFile != ""{ + remoteFileFullPath = finalFile + } + utils.CopyOverCat(applicationName, remoteFileFullPath, localFileFullPath) + if !keepAfterDownload{ + utils.DeleteRemoteFile(applicationName, remoteFileFullPath) + } + } + // We keep this around to make the compiler happy, but commandExecutor.Execute will cause an os.Exit return strings.Join(output, "\n"), err } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..65d7ed8 --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module cf.plugin.ref/requires + +go 1.16 + +require ( + code.cloudfoundry.org/bytefmt v0.0.0-20210608160410-67692ebc98de // indirect + code.cloudfoundry.org/cli v7.1.0+incompatible + github.com/SAP/cf-cli-java-plugin v0.0.0-20210701123331-dc7334389e07 + github.com/SermoDigital/jose v0.9.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/bramvdbogaerde/go-scp v1.0.0 // indirect + github.com/cloudfoundry/bosh-cli v6.4.1+incompatible // indirect + github.com/cloudfoundry/bosh-utils v0.0.264 // indirect + github.com/cppforlife/go-patch v0.2.0 // indirect + github.com/fatih/color v1.12.0 // indirect + github.com/lunixbochs/vtclean v1.0.0 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/onsi/ginkgo v1.16.4 // indirect + github.com/satori/go.uuid v1.2.0 + github.com/simonleung8/flags v0.0.0-20170704170018-8020ed7bcf1a + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/vito/go-interact v1.0.0 // indirect + gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect + utils v1.0.0 +) + +replace utils => ./utils diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..856bbb9 --- /dev/null +++ b/go.sum @@ -0,0 +1,183 @@ +code.cloudfoundry.org/bytefmt v0.0.0-20210608160410-67692ebc98de h1:Gm/tSRP5CPFuoMUo7NDbRMbpN3h9fBNtD6bXC+Lye9I= +code.cloudfoundry.org/bytefmt v0.0.0-20210608160410-67692ebc98de/go.mod h1:YOakoAiZWNbkynB2dKOKvdxVehYGn3EH4UXq/i99hYg= +code.cloudfoundry.org/cli v7.1.0+incompatible h1:1Zn3I+epQBaBvnZAaTudCQQ0WdqcWtjtjEV9MBZP08Y= +code.cloudfoundry.org/cli v7.1.0+incompatible/go.mod h1:e4d+EpbwevNhyTZKybrLlyTvpH+W22vMsmdmcTxs/Fo= +code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= +code.cloudfoundry.org/tlsconfig v0.0.0-20210615191307-5d92ef3894a7 h1:5N6M1WbWH3bknkX80Q/s7eEo5odqjixLAW79Zrrbqu0= +code.cloudfoundry.org/tlsconfig v0.0.0-20210615191307-5d92ef3894a7/go.mod h1:CKI5CV+3MlfcohVSuU3FxXubFyC52lYJGMLnZ2ltvks= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/SAP/cf-cli-java-plugin v0.0.0-20210701123331-dc7334389e07 h1:jJ2UJqPblUUSFxKY1szaiqOjMSDMVHJ8gKhwAHQ70e0= +github.com/SAP/cf-cli-java-plugin v0.0.0-20210701123331-dc7334389e07/go.mod h1:PN5N4sb1BTnup4/Frhu6a4rJe2v7QNXl/i++zj4bkmw= +github.com/SermoDigital/jose v0.9.1 h1:atYaHPD3lPICcbK1owly3aPm0iaJGSGPi0WD4vLznv8= +github.com/SermoDigital/jose v0.9.1/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= +github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bramvdbogaerde/go-scp v1.0.0 h1:YWfdc1H6TDNgXMnvNYTa+NDvQpV6Q4kyImWBfLDyJ6w= +github.com/bramvdbogaerde/go-scp v1.0.0/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0= +github.com/charlievieth/fs v0.0.1 h1:sJqnp1RWguMAojHpyCbZ2KyXNp2ihxGIFPUNb8XDGu8= +github.com/charlievieth/fs v0.0.1/go.mod h1:74vroF06jvR8XMafvi2CYzs8WruHL1axh/qFx7XN5Xw= +github.com/cloudfoundry/bosh-cli v6.4.1+incompatible h1:n5/+NIF9QxvGINOrjh6DmO+GTen78MoCj5+LU9L8bR4= +github.com/cloudfoundry/bosh-cli v6.4.1+incompatible/go.mod h1:rzIB+e1sn7wQL/TJ54bl/FemPKRhXby5BIMS3tLuWFM= +github.com/cloudfoundry/bosh-utils v0.0.264 h1:5mI+4Bo95B+e6GmEJz856ZLUgQ+j6ryZluwzn6uRSgU= +github.com/cloudfoundry/bosh-utils v0.0.264/go.mod h1:P99VEt+fq3jswm7i18Ufm935bjLQIAcigJG3USP16jI= +github.com/cloudfoundry/go-socks5 v0.0.0-20180221174514-54f73bdb8a8e/go.mod h1:PXmcacyJB/pJjSxEl15IU6rEIKXrhZQRzsr0UTkgNNs= +github.com/cloudfoundry/socks5-proxy v0.2.12/go.mod h1:rhtCsX4n12C0f1yMPAl1owiYn+Rftf6cewv+5nDzvao= +github.com/cppforlife/go-patch v0.2.0 h1:Y14MnCQjDlbw7WXT4k+u6DPAA9XnygN4BfrSpI/19RU= +github.com/cppforlife/go-patch v0.2.0/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.4.1-0.20170725030731-8382b23d18db/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.2.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20/go.mod h1:Y3IqE20LKprEpLkXb7gXinJf4vvDdQe/BS8E4kL/dgE= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/simonleung8/flags v0.0.0-20170704170018-8020ed7bcf1a h1:3qgm+2S7MtAhH6xop4yeX/P5QGr+Ss9d+CLErszoCCs= +github.com/simonleung8/flags v0.0.0-20170704170018-8020ed7bcf1a/go.mod h1:lfYEax1IvoGfNjgwTUYQXhLUry2sOHGH+3S7+imSCSI= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/square/certstrap v1.2.0/go.mod h1:CUHqV+fxJW0Y5UQFnnbYwQ7bpKXO1AKbic9g73799yw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0= +github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= +github.com/vito/go-interact v1.0.0 h1:niLW3NjGoMWOayoR6iQ8AxWVM1Q4rR8VGZ1mt6uK3BM= +github.com/vito/go-interact v1.0.0/go.mod h1:W1mz+UVUZScRM3eUjQhEQiLDnQ+yLnXkB2rjBfGPrXg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170721122051-25c4ec802a7d/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/utils/.DS_Store b/utils/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + memory: 1G + path: + buildpack: https://github.com/cloudfoundry/java-buildpack + env: + JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { repository_root: "https://java-buildpack.cloudfoundry.org/openjdk-jdk/bionic/x86_64", version: 11.+ } }' + + `) + + return false, nil + } + + return true, nil +} + +func GetAvailablePath(data string, userpath string)(string, error){ + valid, _ := checkUserPathAvailability(data, userpath) + + if valid { + //fmt.Println("userpath '" + userpath + "' will be used") + return userpath, nil + } + + env, err := readAppEnv(data) + if err != nil{ + return "/tmp", nil + } + + //fmt.Println(string(env[:])) + + var cfAppEnv CFAppEnv + yaml.Unmarshal(env, &cfAppEnv) + + //fmt.Println(result) + + for _,v := range cfAppEnv.VCAPSERVICES.FsStorage{ + for _,v2 := range v.VolumeMounts{ + if v2.Mode=="rw" { + return v2.ContainerDir, nil + } + } + } + + return "/tmp", nil +} + +func CopyOverCat(app string, src string, dest string ) error{ + f, err := os.OpenFile(dest,os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err!=nil{ + fmt.Fprintln(os.Stderr, "error occured during create desination file: " + dest + ", please check you are allowed to create file in the path.") + return err + } + defer f.Close() + + cat := exec.Command("cf", "ssh", app, "-c", "cat " + src ) + + cat.Stdout = f + + err = cat.Start() + if err!=nil{ + fmt.Fprintln(os.Stderr, "error occured during copying dump file: " + src + ", please try again.") + return err + } + + err = cat.Wait() + if err != nil{ + fmt.Fprintln(os.Stderr, "error occured while waiting for the copying complete.") + return err + } + + return nil +} + +func DeleteRemoteFile(app string, path string) error{ + _, err := exec.Command("cf", "ssh", app, "-c", "rm " + path ).Output() + + if err != nil{ + fmt.Fprintln(os.Stderr, "error occured while removing dump file generated: %V", err) + return err + } + + return nil +} + +func FindDumpFile(app string, path string) (string, error){ + cmd := " [ -f '" + path +"' ] && echo '" + path + "' || find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1 " + + output, err := exec.Command("cf", "ssh", app, "-c", cmd).Output() + + if err != nil{ + fmt.Fprintln(os.Stderr, "error occured while checking the generated file: %V", err) + return "", err + } + + return strings.Trim(string(output[:]), "\n"), nil + +} \ No newline at end of file diff --git a/utils/go.mod b/utils/go.mod new file mode 100644 index 0000000..393da42 --- /dev/null +++ b/utils/go.mod @@ -0,0 +1,5 @@ +module utils + +go 1.16 + +require github.com/go-yaml/yaml v2.1.0+incompatible // indirect diff --git a/utils/go.sum b/utils/go.sum new file mode 100644 index 0000000..ee81c01 --- /dev/null +++ b/utils/go.sum @@ -0,0 +1,2 @@ +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= From 02a24dda0c03094aa6a33a1ede5afd0f7755ff78 Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Thu, 29 Jul 2021 20:33:54 +0800 Subject: [PATCH 02/16] fix issues when save local copy --- README.md | 12 ++++++++++-- cf_cli_java_plugin.go | 11 +++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6bef1ec..ea94801 100644 --- a/README.md +++ b/README.md @@ -87,13 +87,21 @@ OPTIONS: -app-instance-index -i [index], select to which instance of the app to connect -dry-run -n, just output to command line what would be executed -keep -k, keep the heap dump in the container; by default the heap dump will be deleted from the container's filesystem after been downloaded + -container-dir -cd, the directory path in the container + -local-dir -ld, the local directory path that the dump file will be saved to -The heap dump or thread dump (depending on what you execute) will be outputted to `std-out`. +the heap dump will be copied if -local-dir is specified(should be full folder path): + +```shell +cf java heap-dump [my-app] --container-dir /var/fspath --local-dir /local/path +``` + +The thread dump will be outputted to `std-out`. You may want to redirect the command's output to file, e.g., by executing: ```shell -cf java heap-dump [my_app] -i [my_instance_index] > heap-dump.hprof +cf java thread-dump [my_app] -i [my_instance_index] > heap-dump.hprof ``` The `-k` flag is invalid when invoking `cf java thread-dump`. diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index d71d5f7..2798a11 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -141,7 +141,8 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator remoteDir := commandFlags.String("container-dir") localDir := commandFlags.String("local-dir") - + copyToLocal := len(localDir) > 0 + arguments := commandFlags.Args() argumentLen := len(arguments) @@ -259,7 +260,13 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if err != nil && finalFile != ""{ remoteFileFullPath = finalFile } - utils.CopyOverCat(applicationName, remoteFileFullPath, localFileFullPath) + if copyToLocal{ + err = utils.CopyOverCat(applicationName, remoteFileFullPath, localFileFullPath) + if err == nil { + fmt.Println("heap dump filed saved to: " + localFileFullPath) + } + } + if !keepAfterDownload{ utils.DeleteRemoteFile(applicationName, remoteFileFullPath) From 71a1837f969d2765516310facf00fbcd267cd2b5 Mon Sep 17 00:00:00 2001 From: zhouwei0115 Date: Fri, 30 Jul 2021 09:34:27 +0800 Subject: [PATCH 03/16] Update README.md Co-authored-by: Tim Gerlach --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea94801..0262941 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ OPTIONS: -local-dir -ld, the local directory path that the dump file will be saved to -the heap dump will be copied if -local-dir is specified(should be full folder path): +The heap dump will be copied if `-local-dir` is specified (should be full folder path): ```shell cf java heap-dump [my-app] --container-dir /var/fspath --local-dir /local/path From 003d3251416eb6098aea568d7cb2d03fe92458b3 Mon Sep 17 00:00:00 2001 From: zhouwei0115 Date: Fri, 30 Jul 2021 09:43:09 +0800 Subject: [PATCH 04/16] Update cf_cli_java_plugin.go Co-authored-by: Tim Gerlach --- cf_cli_java_plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index 2798a11..599e686 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -127,7 +127,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator commandFlags.NewIntFlagWithDefault("app-instance-index", "i", "application `instance` to connect to", -1) commandFlags.NewBoolFlag("keep", "k", "whether to `keep` the heap/thread-dump on the container of the application instance after having downloaded it locally") commandFlags.NewBoolFlag("dry-run", "n", "triggers the `dry-run` mode to show only the cf-ssh command that would have been executed") - commandFlags.NewStringFlag("container-dir", "cd", "specify folder path where the dump file should be stored in the container") + commandFlags.NewStringFlag("container-dir", "cd", "specify the folder path where the dump file should be stored in the container") commandFlags.NewStringFlag("local-dir", "ld", "specify the folder where the dump file will be downloaded") parseErr := commandFlags.Parse(args[1:]...) From 1d6c2feb3e3e87cc5f369e616651d74e79f8d2a5 Mon Sep 17 00:00:00 2001 From: zhouwei0115 Date: Fri, 30 Jul 2021 09:43:57 +0800 Subject: [PATCH 05/16] Update cf_cli_java_plugin.go Co-authored-by: Tim Gerlach --- cf_cli_java_plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index 599e686..75e4c33 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -128,7 +128,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator commandFlags.NewBoolFlag("keep", "k", "whether to `keep` the heap/thread-dump on the container of the application instance after having downloaded it locally") commandFlags.NewBoolFlag("dry-run", "n", "triggers the `dry-run` mode to show only the cf-ssh command that would have been executed") commandFlags.NewStringFlag("container-dir", "cd", "specify the folder path where the dump file should be stored in the container") - commandFlags.NewStringFlag("local-dir", "ld", "specify the folder where the dump file will be downloaded") + commandFlags.NewStringFlag("local-dir", "ld", "specify the folder where the dump file will be downloaded to") parseErr := commandFlags.Parse(args[1:]...) if parseErr != nil { From bf7010827b91415760f5f5cbd3e4d97196d665d6 Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Fri, 30 Jul 2021 16:04:28 +0800 Subject: [PATCH 06/16] update based on review comments --- README.md | 2 +- cf_cli_java_plugin.go | 80 ++++++----- cf_cli_java_plugin_test.go | 97 ++++++++++--- go.mod | 2 + go.sum | 13 ++ utils/.DS_Store | Bin 6148 -> 0 bytes utils/cf_java_plugin_util.go | 9 ++ utils/cfutils.go | 242 +++++++++++++++------------------ utils/fakes/fake_utils_impl.go | 84 ++++++++++++ 9 files changed, 340 insertions(+), 189 deletions(-) delete mode 100644 utils/.DS_Store create mode 100644 utils/cf_java_plugin_util.go create mode 100644 utils/fakes/fake_utils_impl.go diff --git a/README.md b/README.md index 0262941..acde41f 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ OPTIONS: -app-instance-index -i [index], select to which instance of the app to connect -dry-run -n, just output to command line what would be executed -keep -k, keep the heap dump in the container; by default the heap dump will be deleted from the container's filesystem after been downloaded - -container-dir -cd, the directory path in the container + -container-dir -cd, the directory path in the container that the heap dump file will be saved to -local-dir -ld, the local directory path that the dump file will be saved to diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index 75e4c33..909b37d 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -76,18 +76,18 @@ const ( // user facing errors). The CLI will exit 0 if the plugin exits 0 and will exit // 1 should the plugin exit nonzero. func (c *JavaPlugin) Run(cliConnection plugin.CliConnection, args []string) { - _, err := c.DoRun(&commandExecutorImpl{cliConnection: cliConnection}, &uuidGeneratorImpl{}, args) + _, err := c.DoRun(&commandExecutorImpl{cliConnection: cliConnection}, &uuidGeneratorImpl{}, utils.CfJavaPluginUtilImpl{}, args) if err != nil { os.Exit(1) } } // DoRun is an internal method that we use to wrap the cmd package with CommandExecutor for test purposes -func (c *JavaPlugin) DoRun(commandExecutor cmd.CommandExecutor, uuidGenerator uuid.UUIDGenerator, args []string) (string, error) { +func (c *JavaPlugin) DoRun(commandExecutor cmd.CommandExecutor, uuidGenerator uuid.UUIDGenerator, util utils.CfJavaPluginUtilImpl, args []string) (string, error) { traceLogger := trace.NewLogger(os.Stdout, true, os.Getenv("CF_TRACE"), "") ui := terminal.NewUI(os.Stdin, os.Stdout, terminal.NewTeePrinter(os.Stdout), traceLogger) - output, err := c.execute(commandExecutor, uuidGenerator, args) + output, err := c.execute(commandExecutor, uuidGenerator, util, args) if err != nil { ui.Failed(err.Error()) @@ -103,7 +103,7 @@ func (c *JavaPlugin) DoRun(commandExecutor cmd.CommandExecutor, uuidGenerator uu return output, err } -func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator uuid.UUIDGenerator, args []string) (string, error) { +func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator uuid.UUIDGenerator, util utils.CfJavaPluginUtilImpl, args []string) (string, error) { if len(args) == 0 { return "", &InvalidUsageError{message: "No command provided"} } @@ -129,7 +129,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator commandFlags.NewBoolFlag("dry-run", "n", "triggers the `dry-run` mode to show only the cf-ssh command that would have been executed") commandFlags.NewStringFlag("container-dir", "cd", "specify the folder path where the dump file should be stored in the container") commandFlags.NewStringFlag("local-dir", "ld", "specify the folder where the dump file will be downloaded to") - + parseErr := commandFlags.Parse(args[1:]...) if parseErr != nil { return "", &InvalidUsageError{message: fmt.Sprintf("Error while parsing command arguments: %v", parseErr)} @@ -142,7 +142,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator localDir := commandFlags.String("local-dir") copyToLocal := len(localDir) > 0 - + arguments := commandFlags.Args() argumentLen := len(arguments) @@ -158,7 +158,12 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if commandFlags.IsSet("keep") { return "", &InvalidUsageError{message: fmt.Sprintf("The flag %q is not supported for thread-dumps", "keep")} } - break + if commandFlags.IsSet("container-dir") { + return "", &InvalidUsageError{message: fmt.Sprintf("The flag %q is not supported for thread-dumps", "container-dir")} + } + if commandFlags.IsSet("local-dir") { + return "", &InvalidUsageError{message: fmt.Sprintf("The flag %q is not supported for thread-dumps", "local-dir")} + } default: return "", &InvalidUsageError{message: fmt.Sprintf("Unrecognized command %q: supported commands are 'heap-dump' and 'thread-dump' (see cf help)", command)} } @@ -177,21 +182,22 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator } var remoteCommandTokens = []string{JavaDetectionCommand} - remoteFileFullPath := "" - localFileFullPath:= "" + heapdumpFileName := "" + localFileFullPath := "" switch command { case heapDumpCommand: - valid, _ :=utils.CheckRequiredTools(applicationName) - if !valid{ - return "", nil + supported, err := util.CheckRequiredTools(applicationName) + if err != nil || !supported { + return "required tools checking failed", err + } + + fspath, err := util.GetAvailablePath(applicationName, remoteDir) + if err != nil { + return "", err } - - fspath, _ := utils.GetAvailablePath(applicationName, remoteDir) - //fmt.Println(fspath) dumpFile := applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" - heapdumpFileName := fspath + "/" + dumpFile - remoteFileFullPath = fspath + "/" + dumpFile + heapdumpFileName = fspath + "/" + dumpFile localFileFullPath = localDir + "/" + dumpFile remoteCommandTokens = append(remoteCommandTokens, @@ -211,9 +217,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator "OUTPUT=$( ${JMAP_COMMAND} -dump:format=b,file="+heapdumpFileName+" $(pidof java) ) || STATUS_CODE=$?", "if [ ! -s "+heapdumpFileName+" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", - //"echo "+heapdumpFileName, - //"exit 0", - //"fi", + // SAP JVM: Wrap everything in an if statement in case jvmmon is available "JVMMON_COMMAND=`find -executable -name jvmmon | head -1 | tr -d [:space:]`", "elif [ -n \"${JVMMON_COMMAND}\" ]; then true", @@ -223,16 +227,8 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator "SIZE=-1; OLD_SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); while [ \"${SIZE}\" != \"${OLD_SIZE}\" ]; do sleep 3; SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); done", "if [ ! -s \"${HEAP_DUMP_NAME}\" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", - //"echo ${HEAP_DUMP_NAME}", - //"fi", "fi") - //if !keepAfterDownload { - // fmt.Println("delete after download") - // OpenJDK - //remoteCommandTokens = append(remoteCommandTokens, "rm -f "+heapdumpFileName) - // SAP JVM - //remoteCommandTokens = append(remoteCommandTokens, "if [ -n \"${HEAP_DUMP_NAME}\" ]; then rm -f ${HEAP_DUMP_NAME} ${HEAP_DUMP_NAME%.*}.addons; fi") - //} + case threadDumpCommand: // OpenJDK remoteCommandTokens = append(remoteCommandTokens, "JSTACK_COMMAND=`find -executable -name jstack | head -1`; if [ -n \"${JSTACK_COMMAND}\" ]; then ${JSTACK_COMMAND} $(pidof java); exit 0; fi") @@ -253,26 +249,26 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator cfSSHArguments = append(cfSSHArguments, remoteCommand) output, err := commandExecutor.Execute(cfSSHArguments) - - if strings.Compare(command, heapDumpCommand) == 0{ - - finalFile, err := utils.FindDumpFile(applicationName, remoteFileFullPath) - if err != nil && finalFile != ""{ - remoteFileFullPath = finalFile + if command == heapDumpCommand { + + finalFile, err := util.FindDumpFile(applicationName, heapdumpFileName) + if err != nil && finalFile != "" { + heapdumpFileName = finalFile } - if copyToLocal{ - err = utils.CopyOverCat(applicationName, remoteFileFullPath, localFileFullPath) + + if copyToLocal { + err = util.CopyOverCat(applicationName, heapdumpFileName, localFileFullPath) if err == nil { fmt.Println("heap dump filed saved to: " + localFileFullPath) + } else { + fmt.Fprintln(os.Stderr, err.Error()) } } - - if !keepAfterDownload{ - utils.DeleteRemoteFile(applicationName, remoteFileFullPath) + if !keepAfterDownload { + util.DeleteRemoteFile(applicationName, heapdumpFileName) } } - // We keep this around to make the compiler happy, but commandExecutor.Execute will cause an os.Exit return strings.Join(output, "\n"), err } @@ -315,6 +311,8 @@ func (c *JavaPlugin) GetMetadata() plugin.PluginMetadata { "app-instance-index": "-i [index], select to which instance of the app to connect", "keep": "-k, keep the heap dump in the container; by default the heap dump will be deleted from the container's filesystem after been downloaded", "dry-run": "-n, just output to command line what would be executed", + "container-dir": "-cd, the directory path in the container that the heap dump file will be saved to", + "local-dir": "-ld, the local directory path that the dump file will be saved to", }, }, }, diff --git a/cf_cli_java_plugin_test.go b/cf_cli_java_plugin_test.go index 9272ee2..4633533 100644 --- a/cf_cli_java_plugin_test.go +++ b/cf_cli_java_plugin_test.go @@ -3,12 +3,14 @@ package main_test import ( "strings" + . "utils/fakes" + . "github.com/SAP/cf-cli-java-plugin" + + io_helpers "code.cloudfoundry.org/cli/cf/util/testhelpers/io" . "github.com/SAP/cf-cli-java-plugin/cmd/fakes" . "github.com/SAP/cf-cli-java-plugin/uuid/fakes" - io_helpers "code.cloudfoundry.org/cli/util/testhelpers/io" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -53,13 +55,14 @@ var _ = Describe("CfJavaPlugin", func() { subject *JavaPlugin commandExecutor *FakeCommandExecutor uuidGenerator *FakeUUIDGenerator + pluginUtil FakeCfJavaPluginUtil ) BeforeEach(func() { subject = &JavaPlugin{} commandExecutor = new(FakeCommandExecutor) uuidGenerator = new(FakeUUIDGenerator) - + pluginUtil = FakeCfJavaPluginUtil{SshEnabled: true, Jmap_jvmmon_present: true, Container_path_valid: true, Fspath: "/tmp", LocalPathValid: true} uuidGenerator.GenerateReturns("abcd-123456") }) @@ -69,7 +72,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java"}) return output, err }) @@ -89,7 +92,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "heap-dump", "my_app", "ciao"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "ciao"}) return output, err }) @@ -109,7 +112,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "UNKNOWN_COMMAND"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "UNKNOWN_COMMAND"}) return output, err }) @@ -151,7 +154,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "heap-dump", "my_app", "my_file", "ciao"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "my_file", "ciao"}) return output, err }) @@ -171,7 +174,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "heap-dump", "my_app"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app"}) return output, err }) @@ -191,7 +194,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "heap-dump", "my_app", "-i", "4"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "-i", "4"}) return output, err }) @@ -205,13 +208,73 @@ var _ = Describe("CfJavaPlugin", func() { }) + Context("with invalid container direcotry specified", func() { + + It("invoke cf ssh for path check and outputs error", func(done Done) { + defer close(done) + pluginUtil.Container_path_valid = false + output, err, cliOutput := captureOutput(func() (string, error) { + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "--container-dir", "/not/valid/path"}) + return output, err + }) + + Expect(output).To(BeEmpty()) + Expect(err).To(ContainSubstring("the container path specified doesn't exist or have no read and write access, please check and try again later")) + Expect(cliOutput).To(ContainSubstring("the container path specified doesn't exist or have no read and write access, please check and try again later")) + + Expect(commandExecutor.ExecuteCallCount()).To(Equal(0)) + Expect(commandExecutor.ExecuteArgsForCall(0)).To(Equal(0)) + }) + + }) + + Context("with invalid local direcotry specified", func() { + + It("invoke cf ssh for path check and outputs error", func(done Done) { + defer close(done) + pluginUtil.LocalPathValid = false + output, err, cliOutput := captureOutput(func() (string, error) { + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "--local-dir", "/not/valid/path"}) + return output, err + }) + + Expect(output).To(BeEmpty()) + Expect(err).To(ContainSubstring("Error occured during create desination file: /not/valid/path, please check you are allowed to create file in the path.")) + Expect(cliOutput).To(ContainSubstring("Error occured during create desination file: /not/valid/path, please check you are allowed to create file in the path.")) + + Expect(commandExecutor.ExecuteCallCount()).To(Equal(0)) + Expect(commandExecutor.ExecuteArgsForCall(0)).To(Equal(0)) + }) + + }) + + Context("with ssh disabled", func() { + + It("invoke cf ssh for path check and outputs error", func(done Done) { + defer close(done) + pluginUtil.SshEnabled = false + output, err, cliOutput := captureOutput(func() (string, error) { + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "--local-dir", "/valid/path"}) + return output, err + }) + + Expect(output).To(BeEmpty()) + Expect(err).To(ContainSubstring("ssh is not enabled for app: 'my_app', please run below 2 shell commands to enable ssh and try again(please note application should be restarted before take effect):\ncf enable-ssh my_app\ncf restart my_app")) + Expect(cliOutput).To(ContainSubstring("ssh is not enabled for app: 'my_app', please run below 2 shell commands to enable ssh and try again(please note application should be restarted before take effect):\ncf enable-ssh my_app\ncf restart my_app")) + + Expect(commandExecutor.ExecuteCallCount()).To(Equal(0)) + Expect(commandExecutor.ExecuteArgsForCall(0)).To(Equal(0)) + }) + + }) + Context("with the --keep flag", func() { It("keeps the heap-dump on the container", func(done Done) { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "heap-dump", "my_app", "-i", "4", "-k"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "-i", "4", "-k"}) return output, err }) @@ -234,7 +297,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "heap-dump", "my_app", "-i", "4", "-k", "-n"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "heap-dump", "my_app", "-i", "4", "-k", "-n"}) return output, err }) @@ -262,7 +325,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "thread-dump"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "thread-dump"}) return output, err }) @@ -282,7 +345,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "thread-dump", "my_app", "my_file", "ciao"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "thread-dump", "my_app", "my_file", "ciao"}) return output, err }) @@ -302,7 +365,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "thread-dump", "my_app"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "thread-dump", "my_app"}) return output, err }) @@ -324,7 +387,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "thread-dump", "my_app", "-i", "4"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "thread-dump", "my_app", "-i", "4"}) return output, err }) @@ -346,7 +409,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "thread-dump", "my_app", "-i", "4", "-k"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "thread-dump", "my_app", "-i", "4", "-k"}) return output, err }) @@ -366,7 +429,7 @@ var _ = Describe("CfJavaPlugin", func() { defer close(done) output, err, cliOutput := captureOutput(func() (string, error) { - output, err := subject.DoRun(commandExecutor, uuidGenerator, []string{"java", "thread-dump", "my_app", "-i", "4", "-n"}) + output, err := subject.DoRun(commandExecutor, uuidGenerator, pluginUtil, []string{"java", "thread-dump", "my_app", "-i", "4", "-n"}) return output, err }) diff --git a/go.mod b/go.mod index 65d7ed8..b4fe486 100644 --- a/go.mod +++ b/go.mod @@ -16,10 +16,12 @@ require ( github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/onsi/ginkgo v1.16.4 // indirect + github.com/onsi/gomega v1.14.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/simonleung8/flags v0.0.0-20170704170018-8020ed7bcf1a github.com/sirupsen/logrus v1.8.1 // indirect github.com/vito/go-interact v1.0.0 // indirect + golang.org/x/tools v0.1.5 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect utils v1.0.0 ) diff --git a/go.sum b/go.sum index 856bbb9..9e01e24 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -83,6 +84,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= +github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20/go.mod h1:Y3IqE20LKprEpLkXb7gXinJf4vvDdQe/BS8E4kL/dgE= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= @@ -103,6 +106,7 @@ github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVU github.com/vito/go-interact v1.0.0 h1:niLW3NjGoMWOayoR6iQ8AxWVM1Q4rR8VGZ1mt6uK3BM= github.com/vito/go-interact v1.0.0/go.mod h1:W1mz+UVUZScRM3eUjQhEQiLDnQ+yLnXkB2rjBfGPrXg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -112,6 +116,7 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -119,11 +124,14 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -139,7 +147,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= @@ -154,6 +164,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -165,6 +177,7 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/utils/.DS_Store b/utils/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 @@ -131,92 +120,85 @@ func CheckRequiredTools(app string)(bool, error){ JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { repository_root: "https://java-buildpack.cloudfoundry.org/openjdk-jdk/bionic/x86_64", version: 11.+ } }' `) - - return false, nil } return true, nil } -func GetAvailablePath(data string, userpath string)(string, error){ - valid, _ := checkUserPathAvailability(data, userpath) +func (checker CfJavaPluginUtilImpl) GetAvailablePath(data string, userpath string) (string, error) { + if len(userpath) > 0 { + valid, _ := checkUserPathAvailability(data, userpath) + if valid { + return userpath, nil + } - if valid { - //fmt.Println("userpath '" + userpath + "' will be used") - return userpath, nil + return "", errors.New("the container path specified doesn't exist or have no read and write access, please check and try again later") } env, err := readAppEnv(data) - if err != nil{ + if err != nil { return "/tmp", nil } - //fmt.Println(string(env[:])) - var cfAppEnv CFAppEnv - yaml.Unmarshal(env, &cfAppEnv) + json.Unmarshal(env, &cfAppEnv) - //fmt.Println(result) - - for _,v := range cfAppEnv.VCAPSERVICES.FsStorage{ - for _,v2 := range v.VolumeMounts{ - if v2.Mode=="rw" { + for _, v := range cfAppEnv.SystemEnvJSON.VcapServices.FsStorage { + for _, v2 := range v.VolumeMounts { + if v2.Mode == "rw" { return v2.ContainerDir, nil } } } - + return "/tmp", nil } -func CopyOverCat(app string, src string, dest string ) error{ - f, err := os.OpenFile(dest,os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - if err!=nil{ - fmt.Fprintln(os.Stderr, "error occured during create desination file: " + dest + ", please check you are allowed to create file in the path.") - return err +func (checker CfJavaPluginUtilImpl) CopyOverCat(app string, src string, dest string) error { + f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return errors.New("Error occured during create desination file: " + dest + ", please check you are allowed to create file in the path.") } defer f.Close() - cat := exec.Command("cf", "ssh", app, "-c", "cat " + src ) - + cat := exec.Command("cf", "ssh", app, "-c", "cat "+src) + cat.Stdout = f - + err = cat.Start() - if err!=nil{ - fmt.Fprintln(os.Stderr, "error occured during copying dump file: " + src + ", please try again.") - return err + if err != nil { + return errors.New("error occured during copying dump file: " + src + ", please try again.") } err = cat.Wait() - if err != nil{ - fmt.Fprintln(os.Stderr, "error occured while waiting for the copying complete.") - return err + if err != nil { + return errors.New("error occured while waiting for the copying complete") } return nil } -func DeleteRemoteFile(app string, path string) error{ - _, err := exec.Command("cf", "ssh", app, "-c", "rm " + path ).Output() +func (checker CfJavaPluginUtilImpl) DeleteRemoteFile(app string, path string) error { + _, err := exec.Command("cf", "ssh", app, "-c", "rm "+path).Output() + + if err != nil { + return errors.New("error occured while removing dump file generated") - if err != nil{ - fmt.Fprintln(os.Stderr, "error occured while removing dump file generated: %V", err) - return err } return nil } -func FindDumpFile(app string, path string) (string, error){ - cmd := " [ -f '" + path +"' ] && echo '" + path + "' || find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1 " +func (checker CfJavaPluginUtilImpl) FindDumpFile(app string, path string) (string, error) { + cmd := " [ -f '" + path + "' ] && echo '" + path + "' || find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1 " output, err := exec.Command("cf", "ssh", app, "-c", cmd).Output() - - if err != nil{ - fmt.Fprintln(os.Stderr, "error occured while checking the generated file: %V", err) - return "", err + + if err != nil { + return "", errors.New("error occured while checking the generated file") + } return strings.Trim(string(output[:]), "\n"), nil -} \ No newline at end of file +} diff --git a/utils/fakes/fake_utils_impl.go b/utils/fakes/fake_utils_impl.go new file mode 100644 index 0000000..f521751 --- /dev/null +++ b/utils/fakes/fake_utils_impl.go @@ -0,0 +1,84 @@ +package fakes + +import ( + "errors" + "os/exec" + "strings" +) + +type FakeCfJavaPluginUtil struct { + SshEnabled bool + Jmap_jvmmon_present bool + Container_path_valid bool + Fspath string + LocalPathValid bool +} + +func (fakeUtil FakeCfJavaPluginUtil) CheckRequiredTools(app string) (bool, error) { + + if !fakeUtil.SshEnabled { + return false, errors.New("ssh is not enabled for app: '" + app + "', please run below 2 shell commands to enable ssh and try again(please note application should be restarted before take effect):\ncf enable-ssh " + app + "\ncf restart " + app) + } + + if !fakeUtil.Jmap_jvmmon_present { + return false, errors.New(`jvmmon or jmap are required for generating heap dump, you can modify your application manifest.yaml on the 'JBP_CONFIG_OPEN_JDK_JRE' environment variable. This could be done like this: + --- + applications: + - name: + memory: 1G + path: + buildpack: https://github.com/cloudfoundry/java-buildpack + env: + JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { repository_root: "https://java-buildpack.cloudfoundry.org/openjdk-jdk/bionic/x86_64", version: 11.+ } }' + + `) + } + + return true, nil +} + +func (fake FakeCfJavaPluginUtil) GetAvailablePath(data string, userpath string) (string, error) { + if !fake.Container_path_valid && len(userpath) > 0 { + return "", errors.New("the container path specified doesn't exist or have no read and write access, please check and try again later") + } + + if len(fake.Fspath) > 0 { + return fake.Fspath, nil + } + + return "/tmp", nil +} + +func (fake FakeCfJavaPluginUtil) CopyOverCat(app string, src string, dest string) error { + + if !fake.LocalPathValid { + return errors.New("Error occured during create desination file: " + dest + ", please check you are allowed to create file in the path.") + } + + return nil +} + +func (fake FakeCfJavaPluginUtil) DeleteRemoteFile(app string, path string) error { + _, err := exec.Command("cf", "ssh", app, "-c", "rm "+path).Output() + + if err != nil { + return errors.New("error occured while removing dump file generated") + + } + + return nil +} + +func (fake FakeCfJavaPluginUtil) FindDumpFile(app string, path string) (string, error) { + cmd := " [ -f '" + path + "' ] && echo '" + path + "' || find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1 " + + output, err := exec.Command("cf", "ssh", app, "-c", cmd).Output() + + if err != nil { + return "", errors.New("error occured while checking the generated file") + + } + + return strings.Trim(string(output[:]), "\n"), nil + +} From 47820ebf37ab84bbc7dbd6bbc0bf57265cc1fc48 Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Fri, 30 Jul 2021 16:21:36 +0800 Subject: [PATCH 07/16] return err if utils any error --- cf_cli_java_plugin.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index 909b37d..cc444ca 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -261,12 +261,15 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if err == nil { fmt.Println("heap dump filed saved to: " + localFileFullPath) } else { - fmt.Fprintln(os.Stderr, err.Error()) + return "", err } } if !keepAfterDownload { - util.DeleteRemoteFile(applicationName, heapdumpFileName) + err = util.DeleteRemoteFile(applicationName, heapdumpFileName) + if err != nil { + return "", err + } } } // We keep this around to make the compiler happy, but commandExecutor.Execute will cause an os.Exit From 248e3ae7d1d3855df437152442add05f84da0963 Mon Sep 17 00:00:00 2001 From: zhouwei0115 Date: Wed, 11 Aug 2021 10:06:29 +0800 Subject: [PATCH 08/16] Update cf_cli_java_plugin_test.go Co-authored-by: Tim Gerlach --- cf_cli_java_plugin_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cf_cli_java_plugin_test.go b/cf_cli_java_plugin_test.go index 4633533..b3cf9be 100644 --- a/cf_cli_java_plugin_test.go +++ b/cf_cli_java_plugin_test.go @@ -208,7 +208,7 @@ var _ = Describe("CfJavaPlugin", func() { }) - Context("with invalid container direcotry specified", func() { + Context("with invalid container directory specified", func() { It("invoke cf ssh for path check and outputs error", func(done Done) { defer close(done) From 15291e925948eebe9516b0fa6e86431f22747eb9 Mon Sep 17 00:00:00 2001 From: zhouwei0115 Date: Wed, 11 Aug 2021 10:15:29 +0800 Subject: [PATCH 09/16] Update cf_cli_java_plugin_test.go Co-authored-by: Tim Gerlach --- cf_cli_java_plugin_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cf_cli_java_plugin_test.go b/cf_cli_java_plugin_test.go index b3cf9be..0442041 100644 --- a/cf_cli_java_plugin_test.go +++ b/cf_cli_java_plugin_test.go @@ -228,7 +228,7 @@ var _ = Describe("CfJavaPlugin", func() { }) - Context("with invalid local direcotry specified", func() { + Context("with invalid local directory specified", func() { It("invoke cf ssh for path check and outputs error", func(done Done) { defer close(done) From d6b91ebfbcbd6af20a0a04f22a9f90178b6fa9aa Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Wed, 11 Aug 2021 10:18:55 +0800 Subject: [PATCH 10/16] udpate according Tim's comments --- cf_cli_java_plugin.go | 11 ++++++----- cf_cli_java_plugin_test.go | 4 ++-- utils/cfutils.go | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index cc444ca..bc1eb57 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -128,7 +128,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator commandFlags.NewBoolFlag("keep", "k", "whether to `keep` the heap/thread-dump on the container of the application instance after having downloaded it locally") commandFlags.NewBoolFlag("dry-run", "n", "triggers the `dry-run` mode to show only the cf-ssh command that would have been executed") commandFlags.NewStringFlag("container-dir", "cd", "specify the folder path where the dump file should be stored in the container") - commandFlags.NewStringFlag("local-dir", "ld", "specify the folder where the dump file will be downloaded to") + commandFlags.NewStringFlag("local-dir", "ld", "specify the folder where the dump file will be downloaded to, dump file wil not be copied to local if this parameter was not set") parseErr := commandFlags.Parse(args[1:]...) if parseErr != nil { @@ -183,7 +183,6 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator var remoteCommandTokens = []string{JavaDetectionCommand} heapdumpFileName := "" - localFileFullPath := "" switch command { case heapDumpCommand: @@ -196,9 +195,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if err != nil { return "", err } - dumpFile := applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" - heapdumpFileName = fspath + "/" + dumpFile - localFileFullPath = localDir + "/" + dumpFile + heapdumpFileName = fspath + "/" + applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" remoteCommandTokens = append(remoteCommandTokens, // Check file does not already exist @@ -257,12 +254,15 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator } if copyToLocal { + localFileFullPath := localDir + "/" + applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" err = util.CopyOverCat(applicationName, heapdumpFileName, localFileFullPath) if err == nil { fmt.Println("heap dump filed saved to: " + localFileFullPath) } else { return "", err } + } else { + fmt.Println("heap dump will not be copied as parameter `local-dir` was not set") } if !keepAfterDownload { @@ -270,6 +270,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if err != nil { return "", err } + fmt.Println("heap dump filed deleted in app container") } } // We keep this around to make the compiler happy, but commandExecutor.Execute will cause an os.Exit diff --git a/cf_cli_java_plugin_test.go b/cf_cli_java_plugin_test.go index 0442041..4fe5da8 100644 --- a/cf_cli_java_plugin_test.go +++ b/cf_cli_java_plugin_test.go @@ -239,8 +239,8 @@ var _ = Describe("CfJavaPlugin", func() { }) Expect(output).To(BeEmpty()) - Expect(err).To(ContainSubstring("Error occured during create desination file: /not/valid/path, please check you are allowed to create file in the path.")) - Expect(cliOutput).To(ContainSubstring("Error occured during create desination file: /not/valid/path, please check you are allowed to create file in the path.")) + Expect(err).To(ContainSubstring("Error creating local file at : /not/valid/path. Please check that you are allowed to create files at the given local path.")) + Expect(cliOutput).To(ContainSubstring("Error creating local file at : /not/valid/path. Please check that you are allowed to create files at the given local path.")) Expect(commandExecutor.ExecuteCallCount()).To(Equal(0)) Expect(commandExecutor.ExecuteArgsForCall(0)).To(Equal(0)) diff --git a/utils/cfutils.go b/utils/cfutils.go index 53abe00..d8db5bd 100644 --- a/utils/cfutils.go +++ b/utils/cfutils.go @@ -157,7 +157,7 @@ func (checker CfJavaPluginUtilImpl) GetAvailablePath(data string, userpath strin func (checker CfJavaPluginUtilImpl) CopyOverCat(app string, src string, dest string) error { f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { - return errors.New("Error occured during create desination file: " + dest + ", please check you are allowed to create file in the path.") + return errors.New("Error creating local file at " + dest + ". Please check that you are allowed to create files at the given local path.") } defer f.Close() From e319efa8b690a8ab049e386260932f5107c46579 Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Fri, 13 Aug 2021 14:51:10 +0800 Subject: [PATCH 11/16] fix jvmmon check issues --- cf_cli_java_plugin.go | 9 ++++----- utils/cfutils.go | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index bc1eb57..e7a50ba 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -210,18 +210,17 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator */ // OpenJDK: Wrap everything in an if statement in case jmap is available "JMAP_COMMAND=`find -executable -name jmap | head -1 | tr -d [:space:]`", + // SAP JVM: Wrap everything in an if statement in case jvmmon is available + "JVMMON_COMMAND=`find -executable -name jvmmon | head -1 | tr -d [:space:]`", "if [ -n \"${JMAP_COMMAND}\" ]; then true", "OUTPUT=$( ${JMAP_COMMAND} -dump:format=b,file="+heapdumpFileName+" $(pidof java) ) || STATUS_CODE=$?", "if [ ! -s "+heapdumpFileName+" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", - - // SAP JVM: Wrap everything in an if statement in case jvmmon is available - "JVMMON_COMMAND=`find -executable -name jvmmon | head -1 | tr -d [:space:]`", "elif [ -n \"${JVMMON_COMMAND}\" ]; then true", "OUTPUT=$( ${JVMMON_COMMAND} -pid $(pidof java) -c \"dump heap\" ) || STATUS_CODE=$?", "sleep 5", // Writing the heap dump is triggered asynchronously -> give the jvm some time to create the file "HEAP_DUMP_NAME=`find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1`", - "SIZE=-1; OLD_SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); while [ \"${SIZE}\" != \"${OLD_SIZE}\" ]; do sleep 3; SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); done", + "SIZE=-1; OLD_SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); while [ ${SIZE} != ${OLD_SIZE} ]; do OLD_SIZE=${SIZE}; sleep 3; SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); done", "if [ ! -s \"${HEAP_DUMP_NAME}\" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", "fi") @@ -249,7 +248,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if command == heapDumpCommand { finalFile, err := util.FindDumpFile(applicationName, heapdumpFileName) - if err != nil && finalFile != "" { + if err == nil && finalFile != "" { heapdumpFileName = finalFile } diff --git a/utils/cfutils.go b/utils/cfutils.go index d8db5bd..994228b 100644 --- a/utils/cfutils.go +++ b/utils/cfutils.go @@ -103,7 +103,7 @@ func (checker CfJavaPluginUtilImpl) CheckRequiredTools(app string) (bool, error) return false, errors.New("ssh is not enabled for app: '" + app + "', please run below 2 shell commands to enable ssh and try again(please note application should be restarted before take effect):\ncf enable-ssh " + app + "\ncf restart " + app) } - output, err = exec.Command("cf", "ssh", app, "-c", "find -executable -name jvmmon | head -1 | tr -d [:space:] | find -executable -name jmap | head -1 | tr -d [:space:]").Output() + output, err = exec.Command("cf", "ssh", app, "-c", "find -executable -name jvmmon | head -1 | tr -d [:space:] || find -executable -name jmap | head -1 | tr -d [:space:]").Output() if err != nil { return false, errors.New("unknown error occured while checking existence of required tools jvmmon/jmap") @@ -160,7 +160,6 @@ func (checker CfJavaPluginUtilImpl) CopyOverCat(app string, src string, dest str return errors.New("Error creating local file at " + dest + ". Please check that you are allowed to create files at the given local path.") } defer f.Close() - cat := exec.Command("cf", "ssh", app, "-c", "cat "+src) cat.Stdout = f From 6abf1385a32bfa1a941debb368bc1360710b3f58 Mon Sep 17 00:00:00 2001 From: zhouwei0115 Date: Sat, 14 Aug 2021 13:29:53 +0800 Subject: [PATCH 12/16] Update README.md Co-authored-by: Tim Gerlach --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acde41f..67ee5a3 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ OPTIONS: The heap dump will be copied if `-local-dir` is specified (should be full folder path): ```shell -cf java heap-dump [my-app] --container-dir /var/fspath --local-dir /local/path +cf java heap-dump [my-app] -local-dir /local/path [-container-dir /var/fspath] ``` The thread dump will be outputted to `std-out`. From b668c1704c28a8b2993382acaaf53268fe160a20 Mon Sep 17 00:00:00 2001 From: zhouwei0115 Date: Sat, 14 Aug 2021 13:30:14 +0800 Subject: [PATCH 13/16] Update README.md Co-authored-by: Tim Gerlach --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 67ee5a3..1de20fd 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,10 @@ OPTIONS: -local-dir -ld, the local directory path that the dump file will be saved to -The heap dump will be copied if `-local-dir` is specified (should be full folder path): +The heap dump will be copied to a local file if `-local-dir` is specified as a full folder path. Without providing `-local-dir` the heap dump will only be created in the container and not transferred. +To save disk space of the application container, heap dumps are automatically deleted unless the `-keep` option is set. + +Providing `-container-dir` is optional. If specified the plugin will create the heap dump at the given file path in the application container. Without providing this parameter, the heap dump will be created either at `/tmp` or at the file path of a file system service if attached to the container. ```shell cf java heap-dump [my-app] -local-dir /local/path [-container-dir /var/fspath] From 66cb9e91e4e89b9535fdcba8c9f648fb0346c11b Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Sat, 14 Aug 2021 13:31:22 +0800 Subject: [PATCH 14/16] update per Tim's comments --- cf_cli_java_plugin.go | 1 + utils/cfutils.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index e7a50ba..9917b7c 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -250,6 +250,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator finalFile, err := util.FindDumpFile(applicationName, heapdumpFileName) if err == nil && finalFile != "" { heapdumpFileName = finalFile + fmt.Println("successfully created heap dump in application container at: " + heapdumpFileName) } if copyToLocal { diff --git a/utils/cfutils.go b/utils/cfutils.go index 994228b..c13a254 100644 --- a/utils/cfutils.go +++ b/utils/cfutils.go @@ -103,7 +103,7 @@ func (checker CfJavaPluginUtilImpl) CheckRequiredTools(app string) (bool, error) return false, errors.New("ssh is not enabled for app: '" + app + "', please run below 2 shell commands to enable ssh and try again(please note application should be restarted before take effect):\ncf enable-ssh " + app + "\ncf restart " + app) } - output, err = exec.Command("cf", "ssh", app, "-c", "find -executable -name jvmmon | head -1 | tr -d [:space:] || find -executable -name jmap | head -1 | tr -d [:space:]").Output() + output, err = exec.Command("cf", "ssh", app, "-c", "find -executable | grep -E '(.*jmap$)|(.*jvmmon$)'").Output() if err != nil { return false, errors.New("unknown error occured while checking existence of required tools jvmmon/jmap") From 232dc88019d542a5593aae2e29233465e15090ae Mon Sep 17 00:00:00 2001 From: "Zhou, Wei" Date: Mon, 30 Aug 2021 13:33:30 +0800 Subject: [PATCH 15/16] support specify dump file path for jvmmon --- cf_cli_java_plugin.go | 12 +++++++----- utils/cfutils.go | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index 9917b7c..5b2ce27 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -183,6 +183,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator var remoteCommandTokens = []string{JavaDetectionCommand} heapdumpFileName := "" + fspath := remoteDir switch command { case heapDumpCommand: @@ -191,11 +192,11 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator return "required tools checking failed", err } - fspath, err := util.GetAvailablePath(applicationName, remoteDir) + fspath, err = util.GetAvailablePath(applicationName, remoteDir) if err != nil { return "", err } - heapdumpFileName = fspath + "/" + applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" + heapdumpFileName := fspath + "/" + applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" remoteCommandTokens = append(remoteCommandTokens, // Check file does not already exist @@ -217,9 +218,10 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator "if [ ! -s "+heapdumpFileName+" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", "elif [ -n \"${JVMMON_COMMAND}\" ]; then true", - "OUTPUT=$( ${JVMMON_COMMAND} -pid $(pidof java) -c \"dump heap\" ) || STATUS_CODE=$?", + "echo -e 'change command line flag flags=-XX:HeapDumpOnDemandPath="+fspath+"\ndump heap' > setHeapDumpOnDemandPath.sh", + "OUTPUT=$( ${JVMMON_COMMAND} -pid $(pidof java) -cmd \"setHeapDumpOnDemandPath.sh\" ) || STATUS_CODE=$?", "sleep 5", // Writing the heap dump is triggered asynchronously -> give the jvm some time to create the file - "HEAP_DUMP_NAME=`find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1`", + "HEAP_DUMP_NAME=`find "+fspath+" -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1`", "SIZE=-1; OLD_SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); while [ ${SIZE} != ${OLD_SIZE} ]; do OLD_SIZE=${SIZE}; sleep 3; SIZE=$(stat -c '%s' \"${HEAP_DUMP_NAME}\"); done", "if [ ! -s \"${HEAP_DUMP_NAME}\" ]; then echo >&2 ${OUTPUT}; exit 1; fi", "if [ ${STATUS_CODE:-0} -gt 0 ]; then echo >&2 ${OUTPUT}; exit ${STATUS_CODE}; fi", @@ -247,7 +249,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator output, err := commandExecutor.Execute(cfSSHArguments) if command == heapDumpCommand { - finalFile, err := util.FindDumpFile(applicationName, heapdumpFileName) + finalFile, err := util.FindDumpFile(applicationName, heapdumpFileName, fspath) if err == nil && finalFile != "" { heapdumpFileName = finalFile fmt.Println("successfully created heap dump in application container at: " + heapdumpFileName) diff --git a/utils/cfutils.go b/utils/cfutils.go index c13a254..1af0ab5 100644 --- a/utils/cfutils.go +++ b/utils/cfutils.go @@ -188,8 +188,8 @@ func (checker CfJavaPluginUtilImpl) DeleteRemoteFile(app string, path string) er return nil } -func (checker CfJavaPluginUtilImpl) FindDumpFile(app string, path string) (string, error) { - cmd := " [ -f '" + path + "' ] && echo '" + path + "' || find -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1 " +func (checker CfJavaPluginUtilImpl) FindDumpFile(app string, fullpath string, fspath string) (string, error) { + cmd := " [ -f '" + fullpath + "' ] && echo '" + fullpath + "' || find " + fspath + " -name 'java_pid*.hprof' -printf '%T@ %p\\0' | sort -zk 1nr | sed -z 's/^[^ ]* //' | tr '\\0' '\\n' | head -n 1 " output, err := exec.Command("cf", "ssh", app, "-c", cmd).Output() From b4654697046a1ac1f9002451010b6181441457ad Mon Sep 17 00:00:00 2001 From: Tim Gerlach Date: Fri, 1 Oct 2021 16:27:28 +0200 Subject: [PATCH 16/16] Fix issues with OpenJDK VMs --- cf_cli_java_plugin.go | 18 ++++++++++++------ utils/cfutils.go | 3 +-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cf_cli_java_plugin.go b/cf_cli_java_plugin.go index 5b2ce27..ea2dbb8 100644 --- a/cf_cli_java_plugin.go +++ b/cf_cli_java_plugin.go @@ -196,7 +196,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if err != nil { return "", err } - heapdumpFileName := fspath + "/" + applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" + heapdumpFileName = fspath + "/" + applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" remoteCommandTokens = append(remoteCommandTokens, // Check file does not already exist @@ -252,19 +252,25 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator finalFile, err := util.FindDumpFile(applicationName, heapdumpFileName, fspath) if err == nil && finalFile != "" { heapdumpFileName = finalFile - fmt.Println("successfully created heap dump in application container at: " + heapdumpFileName) + fmt.Println("Successfully created heap dump in application container at: " + heapdumpFileName) + } else { + fmt.Println("Failed to find heap dump in application container") + fmt.Println(finalFile) + fmt.Println(heapdumpFileName) + fmt.Println(fspath) + return "", err } if copyToLocal { localFileFullPath := localDir + "/" + applicationName + "-heapdump-" + uuidGenerator.Generate() + ".hprof" err = util.CopyOverCat(applicationName, heapdumpFileName, localFileFullPath) if err == nil { - fmt.Println("heap dump filed saved to: " + localFileFullPath) + fmt.Println("Heap dump filed saved to: " + localFileFullPath) } else { return "", err } } else { - fmt.Println("heap dump will not be copied as parameter `local-dir` was not set") + fmt.Println("Heap dump will not be copied as parameter `local-dir` was not set") } if !keepAfterDownload { @@ -272,7 +278,7 @@ func (c *JavaPlugin) execute(commandExecutor cmd.CommandExecutor, uuidGenerator if err != nil { return "", err } - fmt.Println("heap dump filed deleted in app container") + fmt.Println("Heap dump filed deleted in app container") } } // We keep this around to make the compiler happy, but commandExecutor.Execute will cause an os.Exit @@ -307,7 +313,7 @@ func (c *JavaPlugin) GetMetadata() plugin.PluginMetadata { Commands: []plugin.Command{ { Name: "java", - HelpText: "Obtain a heap-dump or thread-dump from a running, Diego-enabled, SSH-enabled Java application.", + HelpText: "Obtain a heap-dump or thread-dump from a running, SSH-enabled Java application.", // UsageDetails is optional // It is used to show help of usage of each command diff --git a/utils/cfutils.go b/utils/cfutils.go index 1af0ab5..16bfe7b 100644 --- a/utils/cfutils.go +++ b/utils/cfutils.go @@ -194,8 +194,7 @@ func (checker CfJavaPluginUtilImpl) FindDumpFile(app string, fullpath string, fs output, err := exec.Command("cf", "ssh", app, "-c", cmd).Output() if err != nil { - return "", errors.New("error occured while checking the generated file") - + return "", errors.New("error while checking the generated file") } return strings.Trim(string(output[:]), "\n"), nil