diff --git a/CHANGELOG.md b/CHANGELOG.md index bf9f33d59..25eb50559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `tt coredump inspect`: fails for tarantool-ee coredump archive if the source directory is missing. +- `tt pack`: fails if `etcd` or `tcs` are present in the configuration + and not available. ## [2.6.0] - 2024-11-29 diff --git a/cli/cmd/build.go b/cli/cmd/build.go index efcf195c3..02312b06d 100644 --- a/cli/cmd/build.go +++ b/cli/cmd/build.go @@ -30,7 +30,8 @@ func NewBuildCmd() *cobra.Command { args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var runningCtx running.RunningCtx - if err := running.FillCtx(cliOpts, &cmdCtx, &runningCtx, nil, false); err != nil { + err := running.FillCtx(cliOpts, &cmdCtx, &runningCtx, nil, running.ConfigLoadSkip) + if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } return running.ExtractAppNames(runningCtx.Instances), diff --git a/cli/cmd/check.go b/cli/cmd/check.go index b5aed35b5..26d0727c6 100644 --- a/cli/cmd/check.go +++ b/cli/cmd/check.go @@ -32,7 +32,8 @@ func internalCheckModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { } var runningCtx running.RunningCtx - if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, true); err != nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadAll) + if err != nil { return err } diff --git a/cli/cmd/clean.go b/cli/cmd/clean.go index db69c186c..3327602a0 100644 --- a/cli/cmd/clean.go +++ b/cli/cmd/clean.go @@ -104,7 +104,8 @@ func internalCleanModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { } var runningCtx running.RunningCtx - if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, false); err != nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadCluster) + if err != nil { return err } diff --git a/cli/cmd/cluster.go b/cli/cmd/cluster.go index d317c655b..62323f7bc 100644 --- a/cli/cmd/cluster.go +++ b/cli/cmd/cluster.go @@ -673,7 +673,8 @@ func parseAppStr(cmdCtx *cmdcontext.CmdCtx, appStr string) (string, string, stri // Fill context for the entire application. // publish app:inst can work even if the `inst` instance doesn't exist right now. var runningCtx running.RunningCtx - err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, []string{appName}, false) + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, []string{appName}, + running.ConfigLoadCluster) if err != nil { return "", "", "", err } diff --git a/cli/cmd/connect.go b/cli/cmd/connect.go index 65e6b819a..e91b7dc52 100644 --- a/cli/cmd/connect.go +++ b/cli/cmd/connect.go @@ -138,7 +138,8 @@ func resolveConnectOpts(cmdCtx *cmdcontext.CmdCtx, cliOpts *config.CliOpts, // FillCtx returns error if no instances found. var runningCtx running.RunningCtx - fillErr := running.FillCtx(cliOpts, cmdCtx, &runningCtx, []string{target}, false) + fillErr := running.FillCtx(cliOpts, cmdCtx, &runningCtx, []string{target}, + running.ConfigLoadCluster) if fillErr == nil { if len(runningCtx.Instances) > 1 { err = fmt.Errorf("specify instance name") diff --git a/cli/cmd/internal/completion.go b/cli/cmd/internal/completion.go index 17bfd15f5..799b76f17 100644 --- a/cli/cmd/internal/completion.go +++ b/cli/cmd/internal/completion.go @@ -26,7 +26,8 @@ func ValidArgsFunction( directive = cobra.ShellCompDirectiveNoFileComp var runningCtx running.RunningCtx - if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, nil, false); err != nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, nil, running.ConfigLoadSkip) + if err != nil { return } diff --git a/cli/cmd/kill.go b/cli/cmd/kill.go index fbee974c3..c4d3d10b4 100644 --- a/cli/cmd/kill.go +++ b/cli/cmd/kill.go @@ -78,7 +78,8 @@ func internalKillModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { if confirm { var runningCtx running.RunningCtx - if err = running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, false); err != nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadSkip) + if err != nil { return err } diff --git a/cli/cmd/log.go b/cli/cmd/log.go index bbff37ccf..eaaa2f8d2 100644 --- a/cli/cmd/log.go +++ b/cli/cmd/log.go @@ -133,7 +133,8 @@ func internalLogModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var err error var runningCtx running.RunningCtx - if err = running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, false); err != nil { + err = running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadCluster) + if err != nil { return err } diff --git a/cli/cmd/logrotate.go b/cli/cmd/logrotate.go index 00b70f484..cf267c42a 100644 --- a/cli/cmd/logrotate.go +++ b/cli/cmd/logrotate.go @@ -42,7 +42,8 @@ func internalLogrotateModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { } var runningCtx running.RunningCtx - if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, false); err != nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadSkip) + if err != nil { return err } diff --git a/cli/cmd/play.go b/cli/cmd/play.go index 58f0de611..22c92220e 100644 --- a/cli/cmd/play.go +++ b/cli/cmd/play.go @@ -98,8 +98,8 @@ func internalPlayModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { // FillCtx returns error if no instances found. var runningCtx running.RunningCtx - - if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, []string{args[0]}, false); err == nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, []string{args[0]}, running.ConfigLoadAll) + if err == nil { if len(runningCtx.Instances) > 1 { return util.InternalError( "Internal error: specify instance name", diff --git a/cli/cmd/replicaset.go b/cli/cmd/replicaset.go index 0b2548256..c2f2c9177 100644 --- a/cli/cmd/replicaset.go +++ b/cli/cmd/replicaset.go @@ -475,7 +475,7 @@ type replicasetCtx struct { // replicasetFillCtx fills the replicaset command context. func replicasetFillCtx(cmdCtx *cmdcontext.CmdCtx, ctx *replicasetCtx, target string, - isRunningCtxRequired bool) error { + isRunningCtxRequired bool, loadConfig running.ConfigLoad) error { var err error ctx.Orchestrator, err = getOrchestrator() if err != nil { @@ -491,8 +491,7 @@ func replicasetFillCtx(cmdCtx *cmdcontext.CmdCtx, ctx *replicasetCtx, target str SslCiphers: replicasetSslCiphers, } var connOpts connector.ConnectOpts - - err = running.FillCtx(cliOpts, cmdCtx, &ctx.RunningCtx, []string{target}, false) + err = running.FillCtx(cliOpts, cmdCtx, &ctx.RunningCtx, []string{target}, loadConfig) if err == nil { ctx.IsApplication = true if len(ctx.RunningCtx.Instances) == 1 { @@ -514,7 +513,8 @@ func replicasetFillCtx(cmdCtx *cmdcontext.CmdCtx, ctx *replicasetCtx, target str } // Re-fill context for an application. ctx.InstName = instName - err := running.FillCtx(cliOpts, cmdCtx, &ctx.RunningCtx, []string{appName}, false) + err := running.FillCtx(cliOpts, cmdCtx, &ctx.RunningCtx, []string{appName}, + loadConfig) if err != nil { // Should not happen. return err @@ -572,7 +572,7 @@ func replicasetFillCtx(cmdCtx *cmdcontext.CmdCtx, ctx *replicasetCtx, target str // internalReplicasetUpgradeModule is a "upgrade" command for the replicaset module. func internalReplicasetUpgradeModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false, running.ConfigLoadAll); err != nil { return err } if ctx.IsInstanceConnect { @@ -607,7 +607,7 @@ func internalReplicasetDowngradeModule(cmdCtx *cmdcontext.CmdCtx, args []string) downgradeVersion := args[1] var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, target, false); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false, running.ConfigLoadAll); err != nil { return err } if ctx.IsInstanceConnect { @@ -640,7 +640,7 @@ func internalReplicasetDowngradeModule(cmdCtx *cmdcontext.CmdCtx, args []string) // internalReplicasetPromoteModule is a "promote" command for the replicaset module. func internalReplicasetPromoteModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false, running.ConfigLoadAll); err != nil { return err } if !ctx.IsInstanceConnect { @@ -670,7 +670,7 @@ func internalReplicasetPromoteModule(cmdCtx *cmdcontext.CmdCtx, args []string) e // internalReplicasetDemoteModule is a "demote" command for the replicaset module. func internalReplicasetDemoteModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], true); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], true, running.ConfigLoadAll); err != nil { return err } if !ctx.IsApplication { @@ -702,7 +702,7 @@ func internalReplicasetDemoteModule(cmdCtx *cmdcontext.CmdCtx, args []string) er // internalReplicasetStatusModule is a "status" command for the replicaset module. func internalReplicasetStatusModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false, running.ConfigLoadSkip); err != nil { return err } if ctx.IsInstanceConnect { @@ -722,7 +722,7 @@ func internalReplicasetExpelModule(cmdCtx *cmdcontext.CmdCtx, args []string) err return fmt.Errorf("the command expects argument application_name:instance_name") } var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], true); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], true, running.ConfigLoadAll); err != nil { return err } if ctx.IsInstanceConnect { @@ -749,7 +749,7 @@ func internalReplicasetExpelModule(cmdCtx *cmdcontext.CmdCtx, args []string) err // the "replicaset vshard" module. func internalReplicasetBootstrapVShardModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false, running.ConfigLoadAll); err != nil { return err } if ctx.IsInstanceConnect { @@ -776,7 +776,7 @@ func internalReplicasetBootstrapModule(cmdCtx *cmdcontext.CmdCtx, args []string) _, instName, found := strings.Cut(args[0], string(running.InstanceDelimiter)) var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], true); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], true, running.ConfigLoadAll); err != nil { return err } if ctx.IsInstanceConnect { @@ -838,7 +838,7 @@ func internalReplicasetRebootstrapModule(cmdCtx *cmdcontext.CmdCtx, args []strin // internalReplicasetRolesAddModule is a "roles add" command for the replicaset module. func internalReplicasetRolesAddModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false, running.ConfigLoadAll); err != nil { return err } defer ctx.Conn.Close() @@ -881,7 +881,7 @@ func internalReplicasetRolesAddModule(cmdCtx *cmdcontext.CmdCtx, args []string) // internalReplicasetRolesRemoveModule is a "roles remove" command for the replicaset module. func internalReplicasetRolesRemoveModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { var ctx replicasetCtx - if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false); err != nil { + if err := replicasetFillCtx(cmdCtx, &ctx, args[0], false, running.ConfigLoadAll); err != nil { return err } defer ctx.Conn.Close() diff --git a/cli/cmd/start.go b/cli/cmd/start.go index 9ef374529..2984e3d0f 100644 --- a/cli/cmd/start.go +++ b/cli/cmd/start.go @@ -127,7 +127,8 @@ func internalStartModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { } var runningCtx running.RunningCtx - if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, true); err != nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadAll) + if err != nil { return err } diff --git a/cli/cmd/status.go b/cli/cmd/status.go index afa332a06..9257d501f 100644 --- a/cli/cmd/status.go +++ b/cli/cmd/status.go @@ -62,10 +62,11 @@ func internalStatusModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { } var runningCtx running.RunningCtx - if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, false); err != nil { + err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadSkip) + if err != nil { return err } - err := status.Status(runningCtx, opts) + err = status.Status(runningCtx, opts) return err } diff --git a/cli/cmd/stop.go b/cli/cmd/stop.go index 65a6a48b8..0c49a36be 100644 --- a/cli/cmd/stop.go +++ b/cli/cmd/stop.go @@ -2,6 +2,8 @@ package cmd import ( "fmt" + "os" + "github.com/apex/log" "github.com/spf13/cobra" "github.com/tarantool/tt/cli/cmd/internal" @@ -9,7 +11,6 @@ import ( "github.com/tarantool/tt/cli/modules" "github.com/tarantool/tt/cli/running" "github.com/tarantool/tt/cli/util" - "os" ) // NewStopCmd creates stop command. @@ -78,8 +79,8 @@ func internalStopModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { } var runningCtx running.RunningCtx - var err error - if err = running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, false); err != nil { + var err error = running.FillCtx(cliOpts, cmdCtx, &runningCtx, args, running.ConfigLoadSkip) + if err != nil { return err } diff --git a/cli/instances/list.go b/cli/instances/list.go index 3e0fe1bec..291f15ba4 100644 --- a/cli/instances/list.go +++ b/cli/instances/list.go @@ -31,7 +31,7 @@ func ListInstances(cmdCtx *cmdcontext.CmdCtx, cliOpts *config.CliOpts) error { fmt.Printf("instances enabled directory: %s\n", cliOpts.Env.InstancesEnabled) applications, err := running.CollectInstancesForApps(appList, cliOpts, cmdCtx.Cli.ConfigDir, - cmdCtx.Integrity, false) + cmdCtx.Integrity, running.ConfigLoadSkip) if err != nil { return err } diff --git a/cli/pack/opts.go b/cli/pack/opts.go index bbfad2bf0..714fc5230 100644 --- a/cli/pack/opts.go +++ b/cli/pack/opts.go @@ -49,7 +49,7 @@ func initAppsInfo(cliOpts *config.CliOpts, cmdCtx *cmdcontext.CmdCtx, packCtx *P } packCtx.AppList = appList packCtx.AppsInfo, err = running.CollectInstancesForApps(packCtx.AppList, cliOpts, - cmdCtx.Cli.ConfigDir, cmdCtx.Integrity, true) + cmdCtx.Cli.ConfigDir, cmdCtx.Integrity, running.ConfigLoadScripts) if err != nil { return fmt.Errorf("failed to collect applications info: %s", err) } diff --git a/cli/replicaset/rebootstrap.go b/cli/replicaset/rebootstrap.go index a2e87b0ec..c55437150 100644 --- a/cli/replicaset/rebootstrap.go +++ b/cli/replicaset/rebootstrap.go @@ -60,7 +60,7 @@ func cleanDataFiles(instCtx running.InstanceCtx) error { // and starting it again. func Rebootstrap(cmdCtx cmdcontext.CmdCtx, cliOpts config.CliOpts, rbCtx RebootstrapCtx) error { apps, err := running.CollectInstancesForApps([]string{rbCtx.AppName}, &cliOpts, - cmdCtx.Cli.ConfigDir, cmdCtx.Integrity, true) + cmdCtx.Cli.ConfigDir, cmdCtx.Integrity, running.ConfigLoadAll) if err != nil { return fmt.Errorf("cannot collect application instances info: %s", err) } diff --git a/cli/running/running.go b/cli/running/running.go index 01fe2b087..58f1a05f6 100644 --- a/cli/running/running.go +++ b/cli/running/running.go @@ -126,6 +126,22 @@ type providerImpl struct { instanceCtx *InstanceCtx } +// ConfigLoad defines an enumeration of instance script and cluster configuration load types. +type ConfigLoad int + +const ( + // Skip loading cluster config and instances scripts. + ConfigLoadSkip ConfigLoad = iota + // Cluster configuration is required, trigger an error if not. + // Instances scripts could be omitted. + ConfigLoadCluster + // Instance script is required, trigger an error if not. + // Cluster configuration could be omitted. + ConfigLoadScripts + // Load cluster config and instances scripts, trigger an errors if not. + ConfigLoadAll +) + // GetAppPath return application path for the instance. It is a script file path in case of // single instance file-only app, or directory in case of directory-based application. func GetAppPath(instance InstanceCtx) string { @@ -152,7 +168,8 @@ func (provider *providerImpl) updateCtx() error { } var runningCtx RunningCtx - if err = FillCtx(cliOpts, provider.cmdCtx, &runningCtx, args, false); err != nil { + if err = FillCtx( + cliOpts, provider.cmdCtx, &runningCtx, args, ConfigLoadSkip); err != nil { return err } provider.instanceCtx = &runningCtx.Instances[0] @@ -348,7 +365,7 @@ func loadInstanceConfig(configPath, instName string, // collectInstancesFromAppDir collects instances information from application directory. func collectInstancesFromAppDir(appDir string, selectedInstName string, - integrityCtx integrity.IntegrityCtx, instancesScriptsRequired bool) ( + integrityCtx integrity.IntegrityCtx, loadConfig ConfigLoad) ( []InstanceCtx, error, ) { @@ -376,7 +393,7 @@ func collectInstancesFromAppDir(appDir string, selectedInstName string, InstName: filepath.Base(appDir), AppDir: appDir, SingleApp: true}}, nil - } else if instancesScriptsRequired { + } else if loadConfig == ConfigLoadAll || loadConfig == ConfigLoadScripts { return nil, fmt.Errorf("require files are missing in application directory %q: "+ "there must be instances config or the default instance script (%q)", appDir, "init.lua") @@ -409,19 +426,21 @@ func collectInstancesFromAppDir(appDir string, selectedInstName string, } log.Debugf("Instance %q", instance.InstName) - if instance.Configuration, err = loadInstanceConfig(instance.ClusterConfigPath, - instance.InstName, integrityCtx); err != nil { + instance.Configuration, err = loadInstanceConfig(instance.ClusterConfigPath, + instance.InstName, integrityCtx) + if err != nil && (loadConfig == ConfigLoadAll || loadConfig == ConfigLoadCluster) { return instances, fmt.Errorf("error loading instance %q configuration from "+ "config %q: %w", instance.InstName, instance.ClusterConfigPath, err) } instance.SingleApp = false - if instance.InstanceScript, err = findInstanceScriptInAppDir(appDir, instance.InstName, - appDirFiles.clusterCfgPath, appDirFiles.defaultLuaPath); err != nil && - instancesScriptsRequired { + instance.InstanceScript, err = findInstanceScriptInAppDir(appDir, instance.InstName, + appDirFiles.clusterCfgPath, appDirFiles.defaultLuaPath) + if err != nil && (loadConfig == ConfigLoadAll || loadConfig == ConfigLoadScripts) { return instances, fmt.Errorf("cannot find instance script for %q in config %q: %w ", instance.InstName, appDirFiles.clusterCfgPath, err) } + instances = append(instances, instance) } @@ -434,7 +453,9 @@ func collectInstancesFromAppDir(appDir string, selectedInstName string, // CollectInstances searches all instances available in application. func CollectInstances(appName string, applicationsDir string, - integrityCtx integrity.IntegrityCtx, instancesScriptsRequired bool) ([]InstanceCtx, error) { + integrityCtx integrity.IntegrityCtx, loadConfig ConfigLoad) ( + []InstanceCtx, error, +) { // The user can select a specific instance from the application. // Example: `tt status application:server`. selectedInstName := "" @@ -462,8 +483,7 @@ func CollectInstances(appName string, applicationsDir string, appDir = applicationsDir } - return collectInstancesFromAppDir(appDir, selectedInstName, integrityCtx, - instancesScriptsRequired) + return collectInstancesFromAppDir(appDir, selectedInstName, integrityCtx, loadConfig) } // cleanup removes runtime artifacts. @@ -625,7 +645,7 @@ func GetClusterConfigPath(cliOpts *config.CliOpts, // CollectInstancesForApps collects instances information per application. func CollectInstancesForApps(appList []string, cliOpts *config.CliOpts, - ttConfigDir string, integrityCtx integrity.IntegrityCtx, instancesScriptsRequired bool) ( + ttConfigDir string, integrityCtx integrity.IntegrityCtx, loadConfig ConfigLoad) ( map[string][]InstanceCtx, error) { instEnabledPath := cliOpts.Env.InstancesEnabled if cliOpts.Env.InstancesEnabled == "." { @@ -635,7 +655,7 @@ func CollectInstancesForApps(appList []string, cliOpts *config.CliOpts, for _, appName := range appList { appName = strings.TrimSuffix(appName, ".lua") collectedInstances, err := CollectInstances(appName, instEnabledPath, integrityCtx, - instancesScriptsRequired) + loadConfig) if err != nil { return apps, fmt.Errorf("can't collect instance information for %s: %w", appName, err) @@ -676,7 +696,7 @@ func createInstanceDataDirectories(instance InstanceCtx) error { // FillCtx fills the RunningCtx context. func FillCtx(cliOpts *config.CliOpts, cmdCtx *cmdcontext.CmdCtx, - runningCtx *RunningCtx, args []string, instancesScriptsRequired bool) error { + runningCtx *RunningCtx, args []string, loadConfig ConfigLoad) error { var err error if len(args) > 1 && cmdCtx.CommandName != "run" && cmdCtx.CommandName != "connect" && @@ -702,8 +722,8 @@ func FillCtx(cliOpts *config.CliOpts, cmdCtx *cmdcontext.CmdCtx, appList = append(appList, args[0]) } - instances, err := CollectInstancesForApps(appList, cliOpts, - cmdCtx.Cli.ConfigDir, cmdCtx.Integrity, instancesScriptsRequired) + instances, err := CollectInstancesForApps(appList, cliOpts, cmdCtx.Cli.ConfigDir, + cmdCtx.Integrity, loadConfig) if err != nil { return err } diff --git a/cli/running/running_test.go b/cli/running/running_test.go index aca2f4097..4215dc8ce 100644 --- a/cli/running/running_test.go +++ b/cli/running/running_test.go @@ -35,7 +35,7 @@ func Test_CollectInstances(t *testing.T) { instances, err := CollectInstances("script", instancesEnabledPath, integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) require.NoError(t, err) require.Equal(t, 1, len(instances)) require.Equal(t, InstanceCtx{ @@ -50,7 +50,7 @@ func Test_CollectInstances(t *testing.T) { instances, err = CollectInstances("single_inst", instancesEnabledPath, integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) require.NoError(t, err) require.Equal(t, 1, len(instances)) require.Equal(t, InstanceCtx{ @@ -67,7 +67,7 @@ func Test_CollectInstances(t *testing.T) { instances, err = CollectInstances(appName, instancesEnabledPath, integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) require.NoError(t, err) require.Equal(t, 3, len(instances)) assert.True(t, slices.Contains(instances, InstanceCtx{ @@ -103,7 +103,7 @@ func Test_CollectInstances(t *testing.T) { instances, err = CollectInstances("script", instancesEnabledPath, integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) assert.ErrorContains(t, err, "script\" doesn't exist or not a directory") assert.Equal(t, 0, len(instances)) @@ -113,7 +113,7 @@ func Test_CollectInstances(t *testing.T) { instances, err = CollectInstances("script", instancesEnabledPath, integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) assert.NoError(t, err) assert.Equal(t, 1, len(instances)) @@ -121,12 +121,112 @@ func Test_CollectInstances(t *testing.T) { instances, err = CollectInstances("script", instancesEnabledPath, integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) assert.ErrorContains(t, err, "script.lua: permission denied") assert.Equal(t, 1, len(instances)) require.NoError(t, os.Chmod(instancesEnabledPath, 0755)) } +func Test_CollectInstancesInstanceScript(t *testing.T) { + if user, err := user.Current(); err == nil && user.Uid == "0" { + t.Skip("Skipping the test, it shouldn't run as root") + } + tmpDir := t.TempDir() + instancesEnabledPath := filepath.Join(tmpDir, "instances.enabled") + require.NoError(t, os.Mkdir(instancesEnabledPath, 0755)) + + err := os.WriteFile(filepath.Join(instancesEnabledPath, "script.lua"), + []byte("print(42)"), 0644) + require.NoError(t, err) + + cases := []struct { + access os.FileMode + mode ConfigLoad + err string + }{ + { + access: 0666, + mode: ConfigLoadAll, + err: "script.lua: permission denied", + }, + { + access: 0666, + mode: ConfigLoadScripts, + err: "script.lua: permission denied", + }, + { + access: 0755, + mode: ConfigLoadSkip, + }, + { + access: 0755, + mode: ConfigLoadCluster, + }, + { + access: 0755, + mode: ConfigLoadAll, + }, + } + + for _, tc := range cases { + t.Run("test", func(t *testing.T) { + require.NoError(t, os.Chmod(instancesEnabledPath, tc.access)) + instances, err := CollectInstances("script", instancesEnabledPath, + integrity.IntegrityCtx{ + Repository: &mockRepository{}, + }, tc.mode) + if tc.err != "" { + assert.ErrorContains(t, err, tc.err) + } else { + assert.NoError(t, err) + assert.Equal(t, 1, len(instances)) + } + require.NoError(t, os.Chmod(instancesEnabledPath, 0755)) + }) + } +} + +func Test_CollectInstancesEtcdNotAvailable(t *testing.T) { + if user, err := user.Current(); err == nil && user.Uid == "0" { + t.Skip("Skipping the test, it shouldn't run as root") + } + instancesEnabledPath := filepath.Join("testdata", "instances_enabled") + + cases := []struct { + mode ConfigLoad + err string + }{ + { + mode: ConfigLoadAll, + err: "unable to connect to etcd", + }, + { + mode: ConfigLoadCluster, + err: "unable to connect to etcd", + }, + { + mode: ConfigLoadScripts, + }, + { + mode: ConfigLoadSkip, + }, + } + + for _, tc := range cases { + t.Run(tc.err, func(t *testing.T) { + _, err := CollectInstances("config_load", instancesEnabledPath, + integrity.IntegrityCtx{ + Repository: &mockRepository{}, + }, tc.mode) + if tc.err != "" { + assert.ErrorContains(t, err, tc.err) + } else { + assert.NoError(t, err) + } + }) + } +} + func Test_collectAppDirFiles(t *testing.T) { tmpdir := t.TempDir() @@ -189,7 +289,7 @@ func Test_collectInstancesForApps(t *testing.T) { instances, err := CollectInstancesForApps(apps, cliOpts, "/etc/tarantool/", integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) require.NoError(t, err) require.Contains(t, instances, appName) @@ -316,7 +416,7 @@ func Test_collectInstancesForSingleInstApp(t *testing.T) { instances, err := CollectInstancesForApps(apps, cliOpts, "/etc/tarantool/", integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) require.NoError(t, err) require.Equal(t, 1, len(instances)) require.Contains(t, instances, appName) @@ -347,7 +447,7 @@ func Test_collectInstancesSingleInstanceTntCtlLayout(t *testing.T) { instances, err := CollectInstancesForApps(apps, cliOpts, cfgDir, integrity.IntegrityCtx{ Repository: &mockRepository{}, - }, true) + }, ConfigLoadAll) require.NoError(t, err) require.Len(t, instances, 1) require.Contains(t, instances, appName) diff --git a/cli/running/testdata/instances_enabled/config_load/config.yml b/cli/running/testdata/instances_enabled/config_load/config.yml new file mode 100644 index 000000000..32de03818 --- /dev/null +++ b/cli/running/testdata/instances_enabled/config_load/config.yml @@ -0,0 +1,46 @@ +credentials: + users: + guest: + roles: [super] + +iproto: + listen: + - uri: 'unix/:./{{ instance_name }}.iproto' + +groups: + group-001: + replicasets: + replicaset-001: + instances: + instance-001: + database: + mode: rw + replicaset-002: + instances: + instance-002: + console: + socket: ./{{instance_name}}.control + wal: + dir: ./{{instance_name}}_wal_dir + database: + mode: rw + replicaset-003: + instances: + instance-003: + snapshot: + dir: ./{{ instance_name }}_snap_dir + vinyl: + dir: ./{{ instance_name }}_vinyl_dir + database: + mode: rw + +config: + etcd: + endpoints: + - http://localhost:2379 + prefix: /test_app + username: client + password: secret + http: + request: + timeout: 3 diff --git a/cli/running/testdata/instances_enabled/config_load/instances.yml b/cli/running/testdata/instances_enabled/config_load/instances.yml new file mode 100644 index 000000000..c9e5954a9 --- /dev/null +++ b/cli/running/testdata/instances_enabled/config_load/instances.yml @@ -0,0 +1,3 @@ +instance-001: +instance-002: +instance-003: diff --git a/test/integration/pack/test_bundles/vshard_app/instances.enabled/test_app b/test/integration/pack/test_bundles/vshard_app/instances.enabled/test_app new file mode 120000 index 000000000..1ab0d0931 --- /dev/null +++ b/test/integration/pack/test_bundles/vshard_app/instances.enabled/test_app @@ -0,0 +1 @@ +../test_app \ No newline at end of file diff --git a/test/integration/pack/test_bundles/vshard_app/test_app/config.yaml b/test/integration/pack/test_bundles/vshard_app/test_app/config.yaml new file mode 100644 index 000000000..cf7fe68c4 --- /dev/null +++ b/test/integration/pack/test_bundles/vshard_app/test_app/config.yaml @@ -0,0 +1,75 @@ +credentials: + users: + client: + password: 'secret' + roles: [super] + replicator: + password: 'secret' + roles: [replication] + storage: + password: 'secret' + roles: [sharding] + +iproto: + advertise: + peer: + login: replicator + sharding: + login: storage + +sharding: + bucket_count: 3000 + +groups: + storages: + app: + module: storage + sharding: + roles: [storage] + replication: + failover: manual + replicasets: + storage-001: + leader: storage-001-a + instances: + storage-001-a: + iproto: + listen: + - uri: localhost:3301 + storage-001-b: + iproto: + listen: + - uri: localhost:3302 + storage-002: + leader: storage-002-a + instances: + storage-002-a: + iproto: + listen: + - uri: localhost:3303 + storage-002-b: + iproto: + listen: + - uri: localhost:3304 + routers: + app: + module: router + sharding: + roles: [router] + replicasets: + router-001: + instances: + router-001-a: + iproto: + listen: + - uri: localhost:3305 +config: + etcd: + endpoints: + - http://localhost:2379 + prefix: /test_app + username: client + password: secret + http: + request: + timeout: 3 diff --git a/test/integration/pack/test_bundles/vshard_app/test_app/instances.yaml b/test/integration/pack/test_bundles/vshard_app/test_app/instances.yaml new file mode 100644 index 000000000..910b35b9f --- /dev/null +++ b/test/integration/pack/test_bundles/vshard_app/test_app/instances.yaml @@ -0,0 +1,11 @@ +--- +storage-001-a: + +storage-001-b: + +storage-002-a: + +storage-002-b: + +router-001-a: + diff --git a/test/integration/pack/test_bundles/vshard_app/test_app/router.lua b/test/integration/pack/test_bundles/vshard_app/test_app/router.lua new file mode 100644 index 000000000..6d23f34e7 --- /dev/null +++ b/test/integration/pack/test_bundles/vshard_app/test_app/router.lua @@ -0,0 +1,34 @@ +local vshard = require('vshard') +local log = require('log') + +-- Bootstrap the vshard router. +while true do + local ok, err = vshard.router.bootstrap({ + if_not_bootstrapped = true, + }) + if ok then + break + end + log.info(('Router bootstrap error: %s'):format(err)) +end + +-- Put data into the cluster. +function put(id, name, age) + local bucket_id = vshard.router.bucket_id_mpcrc32({id}) + vshard.router.callrw(bucket_id, 'put', {id, bucket_id, name, age}) +end + +-- Get data from the cluster. +function get(id) + local bucket_id = vshard.router.bucket_id_mpcrc32({id}) + return vshard.router.callro(bucket_id, 'get', {id}) +end + +-- Put sample data. +function put_sample_data() + put(1, 'Elizabeth', 12) + put(2, 'Mary', 46) + put(3, 'David', 33) + put(4, 'William', 81) + put(5, 'Jack', 35) +end diff --git a/test/integration/pack/test_bundles/vshard_app/test_app/storage.lua b/test/integration/pack/test_bundles/vshard_app/test_app/storage.lua new file mode 100644 index 000000000..61aecd71c --- /dev/null +++ b/test/integration/pack/test_bundles/vshard_app/test_app/storage.lua @@ -0,0 +1,56 @@ +local vshard = require('vshard') + +-- Create 'customers' space. +box.once('customers', function() + box.schema.create_space('customers', { + format = {{ + name = 'id', + type = 'unsigned' + }, { + name = 'bucket_id', + type = 'unsigned' + }, { + name = 'name', + type = 'string' + }, { + name = 'age', + type = 'number' + }} + }) + box.space.customers:create_index('primary_index', { + parts = {{ + field = 1, + type = 'unsigned' + }} + }) + box.space.customers:create_index('bucket_id', { + parts = {{ + field = 2, + type = 'unsigned' + }}, + unique = false + }) + box.space.customers:create_index('age', { + parts = {{ + field = 4, + type = 'number' + }}, + unique = false + }) +end) + +-- Put data to the 'customers' space. +-- Function should be called by the router. +function put(id, bucket_id, name, age) + box.space.customers:insert({id, bucket_id, name, age}) +end + +-- Get data from the 'customers' space. +-- Function should be called by the router. +function get(id) + local tuple = box.space.customers:get(id) + if tuple == nil then + return nil + end + return {tuple.id, tuple.name, tuple.age} +end diff --git a/test/integration/pack/test_bundles/vshard_app/test_app/test_app-scm-1.rockspec b/test/integration/pack/test_bundles/vshard_app/test_app/test_app-scm-1.rockspec new file mode 100644 index 000000000..245497e8e --- /dev/null +++ b/test/integration/pack/test_bundles/vshard_app/test_app/test_app-scm-1.rockspec @@ -0,0 +1,11 @@ +package = 'test_app' +version = 'scm-1' +source = { + url = '/dev/null', +} +dependencies = { + 'vshard == 0.1.25' +} +build = { + type = 'none'; +} diff --git a/test/integration/pack/test_bundles/vshard_app/tt.yaml b/test/integration/pack/test_bundles/vshard_app/tt.yaml new file mode 100644 index 000000000..5a925d0dd --- /dev/null +++ b/test/integration/pack/test_bundles/vshard_app/tt.yaml @@ -0,0 +1,21 @@ +env: + bin_dir: bin + inc_dir: include + instances_enabled: instances.enabled + restart_on_failure: false + tarantoolctl_layout: false +modules: + directory: modules +app: + run_dir: var/run + log_dir: var/log + wal_dir: var/lib + memtx_dir: var/lib + vinyl_dir: var/lib +ee: + credential_path: "" +templates: +- path: templates +repo: + rocks: "" + distfiles: distfiles diff --git a/test/integration/pack/test_pack.py b/test/integration/pack/test_pack.py index e2ae3b2e5..b6c00fd93 100644 --- a/test/integration/pack/test_pack.py +++ b/test/integration/pack/test_pack.py @@ -55,6 +55,12 @@ def assert_single_app_env(config): assert config["env"]["bin_dir"] == "bin" +def assert_vshard_app_env(config): + assert config["env"]["instances_enabled"] == "instances.enabled" + assert config["env"]["bin_dir"] == "bin" + assert config["env"]["inc_dir"] == "include" + + def assert_artifacts_env(config): assert config["app"]["wal_dir"] == "var/lib" assert config["app"]["vinyl_dir"] == "var/lib" @@ -520,6 +526,36 @@ def prepare_tgz_test_cases(tt_cmd) -> list: ], "check_env": ["myapp", assert_single_app_env, assert_artifacts_env] }, + { + "name": "Vshard app packing", + "bundle_src": "vshard_app", + "cmd": tt_cmd, + "pack_type": "tgz", + "args": ["--name", "test_app"], + "res_file": "test_app-0.1.0.0." + get_arch() + ".tar.gz", + "check_exist": [ + os.path.join("bin", "tarantool"), + os.path.join("bin", "tt"), + os.path.join("instances.enabled", "test_app"), + "tt.yaml", + "test_app/config.yaml", + "test_app/instances.yaml", + "test_app/router.lua", + "test_app/storage.lua", + ], + "check_not_exist": [ + "test_app/test_app-scm-1.rockspec", + "test_app/tt.yaml", + "include", + "modules", + "templates", + os.path.join("test_app", "include"), + os.path.join("test_app", "modules"), + os.path.join("test_app", "templates"), + os.path.join("test_app", "distfiles"), + ], + "check_env": [".", assert_vshard_app_env, assert_artifacts_env] + }, ]