Skip to content

Commit

Permalink
util: check for instances file in app dir
Browse files Browse the repository at this point in the history
It is possible to have an application without init.lua script.
Script names can be specified using instances file. So if the directory
contains instances file, it must be treated as a multi-instance
application.
  • Loading branch information
psergee committed Jun 16, 2023
1 parent 5a7416a commit 7791de7
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed

- Set `compat.fiber_slice_default` to `new` by default in cartridge application template.
- Treat the directory containing the instances file (instances.y[a]ml) as an application.

### Added

Expand Down
12 changes: 7 additions & 5 deletions cli/running/running.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,17 @@ func findInstSeparator(inst string) int {
func getInstancesFromYML(dirPath string, selectedInstName string) ([]InstanceCtx,
error) {
instances := []InstanceCtx{}
instCfgPath := path.Join(dirPath, "instances.yml")

defAppPath := path.Join(dirPath, "init.lua")
defAppExist := false
if _, err := os.Stat(defAppPath); err == nil {
defAppExist = true
}

instCfgPath, err := util.GetYamlFileName(path.Join(dirPath, "instances.yml"), true)
if err != nil {
return nil, err
}
ymlData, err := ioutil.ReadFile(instCfgPath)
if err != nil {
return nil, err
Expand Down Expand Up @@ -282,7 +286,6 @@ func getInstancesFromYML(dirPath string, selectedInstName string) ([]InstanceCtx

// CollectInstances searches all instances available in application.
func CollectInstances(appName string, appDir string) ([]InstanceCtx, error) {
var err error
var appPath string

// The user can select a specific instance from the application.
Expand Down Expand Up @@ -320,9 +323,8 @@ func CollectInstances(appName string, appDir string) ([]InstanceCtx, error) {
}
appPath = luaPath
} else if dirStatErr == nil && dirInfo.IsDir() {
// Search for instances.yml
instCfgPath := path.Join(dirPath, "instances.yml")
if _, err = os.Stat(instCfgPath); err == nil {
// Search for instances.yml.
if _, err := util.GetYamlFileName(path.Join(dirPath, "instances.yml"), true); err == nil {
return getInstancesFromYML(dirPath, selectedInstName)
} else {
appPath = path.Join(dirPath, "init.lua")
Expand Down
25 changes: 14 additions & 11 deletions cli/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,19 +691,22 @@ func IsApp(path string) bool {
return false
}

if !entry.IsDir() && filepath.Ext(entry.Name()) != ".lua" {
return false
} else if !entry.IsDir() && filepath.Ext(entry.Name()) == ".lua" {
return true
}

if _, err = os.Stat(filepath.Join(path, "init.lua")); err == nil && entry.IsDir() {
return true
} else if entry.IsDir() {
return false
if entry.IsDir() {
// Check if the directory contains init.lua script or instances.yml file.
for _, fileTocheck := range [...]string{"init.lua", "instances.yml", "instances.yaml"} {
if fileInfo, err := os.Stat(filepath.Join(path, fileTocheck)); err == nil {
if !fileInfo.IsDir() {
return true
}
}
}
} else {
if filepath.Ext(entry.Name()) == ".lua" {
return true
}
}

return true
return false
}

// CheckRequiredBinaries returns an error if some binaries not found in PATH
Expand Down
28 changes: 27 additions & 1 deletion cli/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ func TestIsApp(t *testing.T) {
isApp bool
}{
{
testName: "Application is directory",
testName: "Application is a directory with init.lua",
createFunc: func() (string, error) {
baseDir := t.TempDir()
filePath := filepath.Join(baseDir, "init.lua")
Expand All @@ -241,6 +241,32 @@ func TestIsApp(t *testing.T) {
},
isApp: true,
},
{
testName: "Application is a directory, no init.lua, instances.yml exists",
createFunc: func() (string, error) {
baseDir := t.TempDir()
filePath := filepath.Join(baseDir, "instances.yml")
_, err := os.Create(filePath)
if err != nil {
return "", err
}
return baseDir, nil
},
isApp: true,
},
{
testName: "Application is a directory, no init.lua, instances.yaml exists",
createFunc: func() (string, error) {
baseDir := t.TempDir()
filePath := filepath.Join(baseDir, "instances.yaml")
_, err := os.Create(filePath)
if err != nil {
return "", err
}
return baseDir, nil
},
isApp: true,
},
{
testName: "Application is file",
createFunc: func() (string, error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
router:

storage:

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
local inst_name = os.getenv('TARANTOOL_INSTANCE_NAME')
local app_name = os.getenv('TARANTOOL_APP_NAME')

while true do
print("custom init file...")
if app_name ~= nil and inst_name ~= nil then
print(app_name .. ":" .. inst_name)
else
print("unknown instance")
end
require("fiber").sleep(1)
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local inst_name = os.getenv('TARANTOOL_INSTANCE_NAME')
local app_name = os.getenv('TARANTOOL_APP_NAME')

while true do
if app_name ~= nil and inst_name ~= nil then
print(app_name .. ":" .. inst_name)
else
print("unknown instance")
end
require("fiber").sleep(1)
end
3 changes: 3 additions & 0 deletions test/integration/running/multi_inst_app_no_init/tt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tt:
app:
instances_enabled: instances.enabled
60 changes: 60 additions & 0 deletions test/integration/running/test_running.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,3 +729,63 @@ def test_running_start(tt_cmd):
# Check that the process was terminated correctly.
instance_process_rc = instance_process.wait(1)
assert instance_process_rc == 0


def test_running_instance_from_multi_inst_app_no_init_script(tt_cmd):
test_app_path_src = os.path.join(os.path.dirname(__file__), "multi_inst_app_no_init")

# Default temporary directory may have very long path. This can cause socket path buffer
# overflow. Create our own temporary directory.
with tempfile.TemporaryDirectory() as tmpdir:
test_env_path = os.path.join(tmpdir, "tt_env")
shutil.copytree(test_app_path_src, test_env_path)

def empty():
pass

def rename():
os.rename(os.path.join(test_env_path, "instances.enabled", "mi_app", "instances.yml"),
os.path.join(test_env_path, "instances.enabled", "mi_app", "instances.yaml"))

for modify_func in [empty, rename]:
modify_func()

# Start the application.
start_cmd = [tt_cmd, "start", "mi_app"]
instance_process = subprocess.Popen(
start_cmd,
cwd=test_env_path,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True
)
start_output = instance_process.stdout.readline()
assert "Starting an instance [mi_app:" in start_output
assert "Starting an instance [mi_app:" in start_output
# Check that the process was terminated correctly.
instance_process_rc = instance_process.wait(5)
assert instance_process_rc == 0

# Check status.
file = wait_file(os.path.join(test_env_path, run_path, "mi_app", "router"),
"router.pid", [])
assert file != ""
file = wait_file(os.path.join(test_env_path, run_path, "mi_app", "storage"),
"storage.pid", [])
assert file != ""

for inst in ["router", "storage"]:
status_cmd = [tt_cmd, "status", "mi_app:" + inst]
status_rc, status_out = run_command_and_get_output(status_cmd, cwd=test_env_path)
assert status_rc == 0
status_out = extract_status(status_out)
assert status_out[f"mi_app:{inst}"]["STATUS"] == "RUNNING"

# Stop the Instance.
stop_cmd = [tt_cmd, "stop", "mi_app"]
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_env_path)
assert stop_rc == 0
assert re.search(r"The Instance mi_app:router \(PID = \d+\) has been terminated.",
stop_out)
assert re.search(r"The Instance mi_app:storage \(PID = \d+\) has been terminated.",
stop_out)

0 comments on commit 7791de7

Please sign in to comment.