Dapr stop and list changes for dapr run -f (#1169)

* dapr stop -f impl

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* add description

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* fix tests

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* fix windows failure

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* Update cmd/stop.go

Co-authored-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com>
Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* review comments

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* review comments

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* change cmd description

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* stop refactoring

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

* trigger pr checks

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>

Signed-off-by: Pravin Pushkar <ppushkar@microsoft.com>
Co-authored-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com>
This commit is contained in:
Pravin Pushkar 2023-01-24 18:28:31 +05:30 committed by GitHub
parent 61bf50cc4c
commit 14c04f9a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 37 deletions

View File

@ -46,6 +46,12 @@ type daprVersion struct {
RuntimeVersion string `json:"Runtime version"`
}
type osType string
const (
windowsOsType osType = "windows"
)
var (
daprVer daprVersion
logAsJSON bool

View File

@ -70,7 +70,7 @@ dapr invoke --unix-domain-socket --app-id target --method sample --verb GET
// TODO(@daixiang0): add Windows support.
if invokeSocket != "" {
if runtime.GOOS == "windows" {
if runtime.GOOS == string(windowsOsType) {
print.FailureStatusEvent(os.Stderr, "The unix-domain-socket option is not supported on Windows")
os.Exit(1)
} else {

View File

@ -69,7 +69,7 @@ dapr publish --publish-app-id myapp --pubsub target --topic sample --data '{"key
client := standalone.NewClient()
// TODO(@daixiang0): add Windows support.
if publishSocket != "" {
if runtime.GOOS == "windows" {
if runtime.GOOS == string(windowsOsType) {
print.FailureStatusEvent(os.Stderr, "The unix-domain-socket option is not supported on Windows")
os.Exit(1)
} else {

View File

@ -18,6 +18,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
@ -92,6 +93,12 @@ dapr run --app-id myapp --app-port 3000 --app-protocol grpc -- go run main.go
# Run sidecar only specifying dapr runtime installation directory
dapr run --app-id myapp --dapr-path /usr/local/dapr
# Run multiple apps by providing path of a run config file
dapr run --run-file dapr.yaml
# Run multiple apps by providing a directory path containing the run config file(dapr.yaml)
dapr run --run-file /path/to/directory
`,
Args: cobra.MinimumNArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
@ -99,7 +106,7 @@ dapr run --app-id myapp --dapr-path /usr/local/dapr
},
Run: func(cmd *cobra.Command, args []string) {
if len(runFilePath) > 0 {
if runtime.GOOS == "windows" {
if runtime.GOOS == string(windowsOsType) {
print.FailureStatusEvent(os.Stderr, "The run command with run file is not supported on Windows")
os.Exit(1)
}
@ -133,7 +140,7 @@ dapr run --app-id myapp --dapr-path /usr/local/dapr
if unixDomainSocket != "" {
// TODO(@daixiang0): add Windows support.
if runtime.GOOS == "windows" {
if runtime.GOOS == string(windowsOsType) {
print.FailureStatusEvent(os.Stderr, "The unix-domain-socket option is not supported on Windows")
os.Exit(1)
} else {
@ -438,7 +445,7 @@ func init() {
RunCmd.Flags().IntVar(&appHealthThreshold, "app-health-threshold", 0, "Number of consecutive failures for the app to be considered unhealthy")
RunCmd.Flags().BoolVar(&enableAPILogging, "enable-api-logging", false, "Log API calls at INFO verbosity. Valid values are: true or false")
RunCmd.Flags().StringVar(&apiListenAddresses, "dapr-listen-addresses", "", "Comma separated list of IP addresses that sidecar will listen to")
RunCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the configuration file for the apps to run")
RunCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the run template file for the list of apps to run")
RootCmd.AddCommand(RunCmd)
}
@ -503,11 +510,13 @@ func executeRun(runFilePath string, apps []runfileconfig.App) (bool, error) {
runStates = append(runStates, runState)
// Metadata API is only available if app has started listening to port, so wait for app to start before calling metadata API.
// The PID is put as 0, so as to not kill CLI process when any one of the apps is stopped.
_ = putCLIProcessIDInMeta(runState, 0)
putCLIProcessIDInMeta(runState, os.Getpid())
// Update extended metadata with run file path.
putRunFilePathInMeta(runState, runFilePath)
if runState.AppCMD.Command != nil {
_ = putAppCommandInMeta(runConfig, runState)
putAppCommandInMeta(runConfig, runState)
}
print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogSuccess, "You're up and running! Dapr logs will appear here.\n")
logInformationalStatusToStdout(app)
@ -885,28 +894,37 @@ func killAppProcess(runE *runExec.RunExec) error {
}
// putCLIProcessIDInMeta puts the CLI process ID in metadata so that it can be used by the CLI to stop the CLI process.
func putCLIProcessIDInMeta(runE *runExec.RunExec, pid int) error {
// For now putting this as 0, since we do not want the dapr stop command for a single to kill the CLI process,
// thereby killing all the apps that are running via dapr run -f.
func putCLIProcessIDInMeta(runE *runExec.RunExec, pid int) {
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Updating metadata for cliPID: %d", pid)
err := metadata.Put(runE.DaprHTTPPort, "cliPID", strconv.Itoa(pid), runE.AppID, unixDomainSocket)
if err != nil {
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for cliPID: %s", err.Error())
return err
}
return nil
}
// putAppCommandInMeta puts the app command in metadata so that it can be used by the CLI to stop the app.
func putAppCommandInMeta(runConfig standalone.RunConfig, runState *runExec.RunExec) error {
func putAppCommandInMeta(runConfig standalone.RunConfig, runE *runExec.RunExec) {
appCommand := strings.Join(runConfig.Command, " ")
print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogInfo, "Updating metadata for app command: %s", appCommand)
err := metadata.Put(runState.DaprHTTPPort, "appCommand", appCommand, runState.AppID, runConfig.UnixDomainSocket)
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Updating metadata for app command: %s", appCommand)
err := metadata.Put(runE.DaprHTTPPort, "appCommand", appCommand, runE.AppID, runConfig.UnixDomainSocket)
if err != nil {
print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for appCommand: %s", err.Error())
return err
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for appCommand: %s", err.Error())
return
}
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogSuccess, "You're up and running! Dapr logs will appear here.\n")
}
// putRunFilePathInMeta puts the absolute path of run file in metadata so that it can be used by the CLI to stop all apps started by this run file.
func putRunFilePathInMeta(runE *runExec.RunExec, runFilePath string) {
runFilePath, err := filepath.Abs(runFilePath)
if err != nil {
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not get absolute path for run file: %s", err.Error())
return
}
err = metadata.Put(runE.DaprHTTPPort, "runTemplatePath", runFilePath, runE.AppID, unixDomainSocket)
if err != nil {
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for runFile: %s", err.Error())
}
print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogSuccess, "You're up and running! Dapr logs will appear here.\n")
return nil
}
// getRunFilePath returns the path to the run file.

View File

@ -14,7 +14,10 @@ limitations under the License.
package cmd
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/spf13/cobra"
@ -30,13 +33,44 @@ var StopCmd = &cobra.Command{
Example: `
# Stop Dapr application
dapr stop --app-id <ID>
# Stop multiple apps by providing a run config file
dapr stop --run-file dapr.yaml
# Stop multiple apps by providing a directory path containing the run config file(dapr.yaml)
dapr stop --run-file /path/to/directory
`,
Run: func(cmd *cobra.Command, args []string) {
var err error
if len(runFilePath) > 0 {
if runtime.GOOS == string(windowsOsType) {
print.FailureStatusEvent(os.Stderr, "Stop command with run file is not supported on Windows")
os.Exit(1)
}
runFilePath, err = getRunFilePath(runFilePath)
if err != nil {
print.FailureStatusEvent(os.Stderr, "Failed to get run file path: %v", err)
os.Exit(1)
}
err = executeStopWithRunFile(runFilePath)
if err != nil {
print.FailureStatusEvent(os.Stderr, "Failed to stop Dapr and app processes: %s", err)
} else {
print.SuccessStatusEvent(os.Stdout, "Dapr and app processes stopped successfully")
}
return
}
if stopAppID != "" {
args = append(args, stopAppID)
}
apps, err := standalone.List()
if err != nil {
print.FailureStatusEvent(os.Stderr, "failed to get list of apps started by dapr : %s", err)
os.Exit(1)
}
cliPIDToNoOfApps := standalone.GetCLIPIDCountMap(apps)
for _, appID := range args {
err := standalone.Stop(appID)
err = standalone.Stop(appID, cliPIDToNoOfApps, apps)
if err != nil {
print.FailureStatusEvent(os.Stderr, "failed to stop app id %s: %s", appID, err)
} else {
@ -48,6 +82,15 @@ dapr stop --app-id <ID>
func init() {
StopCmd.Flags().StringVarP(&stopAppID, "app-id", "a", "", "The application id to be stopped")
StopCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the run template file for the list of apps to stop")
StopCmd.Flags().BoolP("help", "h", false, "Print this help message")
RootCmd.AddCommand(StopCmd)
}
func executeStopWithRunFile(runFilePath string) error {
absFilePath, err := filepath.Abs(runFilePath)
if err != nil {
return fmt.Errorf("failed to get absolute file path for %s: %w", runFilePath, err)
}
return standalone.StopAppsWithRunFile(absFilePath)
}

View File

@ -41,6 +41,7 @@ type ListOutput struct {
CliPID int `csv:"CLI PID" json:"cliPid" yaml:"cliPid"`
MaxRequestBodySize int `csv:"-" json:"maxRequestBodySize" yaml:"maxRequestBodySize"` // Additional field, not displayed in table.
HTTPReadBufferSize int `csv:"-" json:"httpReadBufferSize" yaml:"httpReadBufferSize"` // Additional field, not displayed in table.
RunTemplatePath string `csv:"RUN_TEMPLATE_PATH" json:"runTemplatePath" yaml:"runTemplatePath"`
}
func (d *daprProcess) List() ([]ListOutput, error) {
@ -99,11 +100,13 @@ func List() ([]ListOutput, error) {
appID := argumentsMap["--app-id"]
appCmd := ""
cliPIDString := ""
runTemplatePath := ""
socket := argumentsMap["--unix-domain-socket"]
appMetadata, err := metadata.Get(httpPort, appID, socket)
if err == nil {
appCmd = appMetadata.Extended["appCommand"]
cliPIDString = appMetadata.Extended["cliPID"]
runTemplatePath = appMetadata.Extended["runTemplatePath"]
}
// Parse functions return an error on bad input.
@ -134,6 +137,7 @@ func List() ([]ListOutput, error) {
Command: utils.TruncateString(appCmd, 20),
MaxRequestBodySize: maxRequestBodySize,
HTTPReadBufferSize: httpReadBufferSize,
RunTemplatePath: runTemplatePath,
}
// filter only dashboard instance.
@ -156,3 +160,12 @@ func getIntArg(argMap map[string]string, argKey string, argDef int) int {
}
return argDef
}
// GetCLIPIDCountMap returns a map of CLI PIDs to number of apps started with it.
func GetCLIPIDCountMap(apps []ListOutput) map[int]int {
cliPIDCountMap := make(map[int]int, len(apps))
for _, app := range apps {
cliPIDCountMap[app.CliPID]++
}
return cliPIDCountMap
}

View File

@ -23,19 +23,15 @@ import (
)
// Stop terminates the application process.
func Stop(appID string) error {
apps, err := List()
if err != nil {
return err
}
func Stop(appID string, cliPIDToNoOfApps map[int]int, apps []ListOutput) error {
for _, a := range apps {
if a.AppID == appID {
var pid string
// Kill the Daprd process if Daprd was started without CLI, otherwise
// kill the CLI process which also kills the associated Daprd process.
if a.CliPID == 0 {
if a.CliPID == 0 || cliPIDToNoOfApps[a.CliPID] > 1 {
pid = fmt.Sprintf("%v", a.DaprdPID)
cliPIDToNoOfApps[a.CliPID]--
} else {
pid = fmt.Sprintf("%v", a.CliPID)
}
@ -45,6 +41,20 @@ func Stop(appID string) error {
return err
}
}
return fmt.Errorf("couldn't find app id %s", appID)
}
// StopAppsWithRunFile terminates the daprd and application processes with the given run file.
func StopAppsWithRunFile(runTemplatePath string) error {
apps, err := List()
if err != nil {
return err
}
for _, a := range apps {
if a.RunTemplatePath == runTemplatePath {
_, err := utils.RunCmdAndWait("kill", fmt.Sprintf("%v", a.CliPID))
return err
}
}
return fmt.Errorf("couldn't find apps with run file %q", runTemplatePath)
}

View File

@ -14,6 +14,7 @@ limitations under the License.
package standalone
import (
"errors"
"fmt"
"syscall"
@ -21,12 +22,7 @@ import (
)
// Stop terminates the application process.
func Stop(appID string) error {
apps, err := List()
if err != nil {
return err
}
func Stop(appID string, cliPIDToNoOfApps map[int]int, apps []ListOutput) error {
for _, a := range apps {
if a.AppID == appID {
eventName, _ := syscall.UTF16FromString(fmt.Sprintf("dapr_cli_%v", a.CliPID))
@ -39,6 +35,10 @@ func Stop(appID string) error {
return err
}
}
return fmt.Errorf("couldn't find app id %s", appID)
}
// StopAppsWithRunFile terminates the daprd and application processes with the given run file.
func StopAppsWithRunFile(runFilePath string) error {
return errors.New("stopping apps with run template file is not supported on windows")
}

View File

@ -39,6 +39,7 @@ func TestStandaloneStop(t *testing.T) {
output, err := cmdStop("dapr_e2e_stop", "-p", "test")
require.Error(t, err, "expected error on stop with unknown flag")
require.Contains(t, output, "Error: unknown shorthand flag: 'p' in -p\nUsage:", "expected usage to be printed")
require.Contains(t, output, "-a, --app-id string The application id to be stopped", "expected usage to be printed")
require.Contains(t, output, "-a, --app-id string", "expected usage to be printed")
require.Contains(t, output, "-f, --run-file string", "expected usage to be printed")
})
}