Skip to content

Commit

Permalink
start: add a "launcher" for the tarantool instance
Browse files Browse the repository at this point in the history
This patch adds a "launcher" - an intermediate lua script that
runs under tarantool and does some preparation (like creating
"console socket", wrapping `box.cfg`...) before launching the
application instance.

Part of tarantool#7
  • Loading branch information
LeonidVas committed Jan 31, 2022
1 parent c406201 commit bd5d943
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
__pycache__
.DS_Store
/tt
**/*_gen.go
51 changes: 51 additions & 0 deletions cli/codegen/generate_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"io/ioutil"

"github.com/apex/log"
"github.com/dave/jennifer/jen"
)

type generateLuaCodeOpts struct {
PackageName string
FileName string
PackagePath string
VariablesMap map[string]string
}

var luaCodeFiles = []generateLuaCodeOpts{
{
PackageName: "running",
FileName: "cli/running/lua_code_gen.go",
VariablesMap: map[string]string{
"instanceLauncher": "cli/running/lua/launcher.lua",
},
},
}

func generateLuaCodeVar() error {
for _, opts := range luaCodeFiles {
f := jen.NewFile(opts.PackageName)
f.Comment("This file is generated! DO NOT EDIT\n")

for key, val := range opts.VariablesMap {
content, err := ioutil.ReadFile(val)
if err != nil {
return err
}

f.Var().Id(key).Op("=").Lit(string(content))
}

f.Save(opts.FileName)
}

return nil
}

func main() {
if err := generateLuaCodeVar(); err != nil {
log.Errorf("Error while generating lua code string variables: %s", err)
}
}
5 changes: 5 additions & 0 deletions cli/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ type CliCtx struct {
type RunningCtx struct {
// Path to an application.
AppPath string
// Directory that stores various instance runtime artifacts like
// console socket, PID file, etc.
RunDir string
// If the instance is started under the watchdog it should
// restart on if it crashes.
Restartable bool
// Control UNIX socket for started instance.
ConsoleSocket string
}
3 changes: 3 additions & 0 deletions cli/modules/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Config struct {
// directory: path/to
//...app:
// available: path
// run_dir: path
// restart_on_failure: bool

type modulesOpts struct {
Expand All @@ -31,6 +32,7 @@ type modulesOpts struct {

type appOpts struct {
InstancesAvailable string `mapstructure:"instances_available"`
RunDir string `mapstructure:"run_dir"`
Restartable bool `mapstructure:"restart_on_failure"`
}

Expand All @@ -46,6 +48,7 @@ func getDefaultCliOpts() *CliOpts {
}
app := appOpts{
InstancesAvailable: "",
RunDir: "",
Restartable: false,
}
return &CliOpts{Modules: &modules, App: &app}
Expand Down
11 changes: 8 additions & 3 deletions cli/running/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ type Instance struct {
}

// NewInstance creates an Instance.
func NewInstance(tarantoolPath string, appPath string, env []string) (*Instance, error) {
func NewInstance(tarantoolPath string, appPath string, console_sock string,
env []string) (*Instance, error) {
// Check if tarantool binary exists.
if _, err := exec.LookPath(tarantoolPath); err != nil {
return nil, err
Expand All @@ -42,7 +43,8 @@ func NewInstance(tarantoolPath string, appPath string, env []string) (*Instance,
return nil, err
}

inst := Instance{tarantoolPath: tarantoolPath, appPath: appPath, env: env}
inst := Instance{tarantoolPath: tarantoolPath, appPath: appPath,
consoleSocket: console_sock, env: env}
return &inst, nil
}

Expand Down Expand Up @@ -78,9 +80,12 @@ func (inst *Instance) Wait() error {

// Start starts the Instance with the specified parameters.
func (inst *Instance) Start() error {
inst.Cmd = exec.Command(inst.tarantoolPath, inst.appPath)
inst.Cmd = exec.Command(inst.tarantoolPath, "-e", instanceLauncher)
inst.Cmd.Stdout = os.Stdout
inst.Cmd.Stderr = os.Stderr
inst.Cmd.Env = append(os.Environ(), "TT_CLI_INSTANCE="+inst.appPath)
inst.Cmd.Env = append(inst.Cmd.Env,
"TT_CLI_CONSOLE_SOCKET="+inst.consoleSocket)

// Start an Instance.
if err := inst.Cmd.Start(); err != nil {
Expand Down
21 changes: 17 additions & 4 deletions cli/running/instance_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package running

import (
"net"
"os"
"os/exec"
"path"
"path/filepath"
"testing"
"time"

Expand All @@ -16,7 +18,7 @@ const (
)

// startTestInstance starts instance for the test.
func startTestInstance(t *testing.T) *Instance {
func startTestInstance(t *testing.T, consoleSock string) *Instance {
assert := assert.New(t)

appPath := path.Join(instTestAppDir, instTestAppName+".lua")
Expand All @@ -26,7 +28,7 @@ func startTestInstance(t *testing.T) *Instance {
tarantoolBin, err := exec.LookPath("tarantool")
assert.Nilf(err, `Can't find a tarantool binary. Error: "%v".`, err)

inst, err := NewInstance(tarantoolBin, appPath, os.Environ())
inst, err := NewInstance(tarantoolBin, appPath, consoleSock, os.Environ())
assert.Nilf(err, `Can't create an instance. Error: "%v".`, err)

err = inst.Start()
Expand All @@ -45,14 +47,25 @@ func cleanupTestInstance(inst *Instance) {
if inst.IsAlive() {
inst.Cmd.Process.Kill()
}
if _, err := os.Stat(inst.consoleSocket); err == nil {
os.Remove(inst.consoleSocket)
}
}

func TestInstanceBase(t *testing.T) {
assert := assert.New(t)

inst := startTestInstance(t)
binPath, err := os.Executable()
assert.Nilf(err, `Can't get the path to the executable. Error: "%v".`, err)
consoleSock := filepath.Join(filepath.Dir(binPath), "test.sock")

inst := startTestInstance(t, consoleSock)
t.Cleanup(func() { cleanupTestInstance(inst) })

err := inst.Stop(30 * time.Second)
conn, err := net.Dial("unix", consoleSock)
assert.Nilf(err, `Can't connect to console socket. Error: "%v".`, err)
conn.Close()

err = inst.Stop(30 * time.Second)
assert.Nilf(err, `Can't stop the instance. Error: "%v".`, err)
}
50 changes: 50 additions & 0 deletions cli/running/lua/launcher.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--- This is a launch script that does the necessary preparation
-- before launching an instance.

-- The script is delivered inside the "tt" binary and is launched
-- to execution via the `-e` flag when starting the application instance.
-- AFAIU, due to such method of launching, we can reach the limit of the
-- command line length ("ARG_MAX") and in this case we will have to create
-- a file with the appropriate code. But, in the real world this limit is
-- quite high (I looked at it on several machines - it equals 2097152)
-- and we can not create a workaround for this situation yet.
--
-- Several useful links:
-- https://porkmail.org/era/unix/arg-max.html
-- https://unix.stackexchange.com/a/120842

local os = require('os')
local console = require('console')
local log = require('log')
local title = require('title')


--- Start an Instance. The "init" file of the Instance passes
-- throught "TT_CLI_INSTANCE".
local function start_instance()
local instance_path = os.getenv('TT_CLI_INSTANCE')
if instance_path == nil then
log.error('Failed to get instance path')
os.exit(1)
end
title.update{
script_name = instance_path,
__defer_update = true
}

-- Preparation of the "console" socket.
local console_sock = os.getenv('TT_CLI_CONSOLE_SOCKET')
if console_sock ~= nil and console_sock ~= '' then
console.listen(console_sock)
end

-- Start the Instance.
local success, data = pcall(dofile, instance_path)
if not success then
log.error('Failed to run instance: %s', instance_path)
os.exit(1)
end
return 0
end

start_instance()
26 changes: 24 additions & 2 deletions cli/running/running.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"time"

"github.com/tarantool/tt/cli/context"
Expand All @@ -13,9 +14,12 @@ import (

// findAppFile searches of an application init file.
func findAppFile(appName string, cliOpts *modules.CliOpts) (string, error) {
var err error
appDir := cliOpts.App.InstancesAvailable
if appDir == "" {
appDir = "."
if appDir, err = os.Getwd(); err != nil {
return "", err
}
}

var appPath string
Expand Down Expand Up @@ -53,6 +57,13 @@ func findAppFile(appName string, cliOpts *modules.CliOpts) (string, error) {
return appPath, nil
}

// cleanup removes runtime artifacts.
func cleanup(ctx *context.Ctx) {
if _, err := os.Stat(ctx.Running.ConsoleSocket); err == nil {
os.Remove(ctx.Running.ConsoleSocket)
}
}

// FillCtx fills the RunningCtx context.
func FillCtx(cliOpts *modules.CliOpts, ctx *context.Ctx, args []string) error {
if len(args) != 1 {
Expand All @@ -67,13 +78,24 @@ func FillCtx(cliOpts *modules.CliOpts, ctx *context.Ctx, args []string) error {

ctx.Running.AppPath = appPath

runDir := cliOpts.App.RunDir
if runDir == "" {
if runDir, err = os.Getwd(); err != nil {
return fmt.Errorf(`Can't get the "RunDir: %s"`, err)
}
}
ctx.Running.RunDir = runDir
ctx.Running.ConsoleSocket = filepath.Join(runDir, appName+".control")

return nil
}

// Start an Instance.
func Start(ctx *context.Ctx) error {
defer cleanup(ctx)

inst, err := NewInstance(ctx.Cli.TarantoolExecutable,
ctx.Running.AppPath, os.Environ())
ctx.Running.AppPath, ctx.Running.ConsoleSocket, os.Environ())
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/running/watchdog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func createTestWatchdog(t *testing.T, restartable bool) *Watchdog {
tarantoolBin, err := exec.LookPath("tarantool")
assert.Nilf(err, `Can't find a tarantool binary. Error: "%v".`, err)

inst, err := NewInstance(tarantoolBin, appPath, os.Environ())
inst, err := NewInstance(tarantoolBin, appPath, "", os.Environ())
assert.Nilf(err, `Can't create an instance. Error: "%v".`, err)

wd := NewWatchdog(inst, restartable, wdTestRestartTimeout)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.17

require (
github.com/apex/log v1.9.0
github.com/dave/jennifer v1.4.1
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/hashicorp/go-version v1.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw=
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
19 changes: 19 additions & 0 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ var (
goExecutableName = "go"
pythonExecutableName = "python3"
ttExecutableName = "tt"

generateModePath = filepath.Join(packagePath, "codegen", "generate_code.go")
)

func init() {
Expand All @@ -65,6 +67,8 @@ func init() {
func Build() error {
fmt.Println("Building tt...")

mg.Deps(GenerateGoCode)

err := sh.RunWith(
getBuildEnvironment(), goExecutableName, "build",
"-o", ttExecutableName,
Expand All @@ -85,6 +89,8 @@ func Build() error {
func Lint() error {
fmt.Println("Running go vet...")

mg.Deps(GenerateGoCode)

if err := sh.RunV(goExecutableName, "vet", packagePath); err != nil {
return err
}
Expand All @@ -102,6 +108,8 @@ func Lint() error {
func Unit() error {
fmt.Println("Running unit tests...")

mg.Deps(GenerateGoCode)

if mg.Verbose() {
return sh.RunV(goExecutableName, "test", "-v", fmt.Sprintf("%s/...", packagePath))
}
Expand All @@ -128,6 +136,17 @@ func Clean() {
os.Remove(ttExecutableName)
}

// GenerateGoCode generates code from lua files.
func GenerateGoCode() error {
err := sh.RunWith(getBuildEnvironment(), goExecutableName, "run", generateModePath)

if err != nil {
return err
}

return nil
}

// getDefaultConfigPath returns the path to the configuration file,
// determining it based on the OS.
func getDefaultConfigPath() string {
Expand Down

0 comments on commit bd5d943

Please sign in to comment.