295 lines
6.0 KiB
Go

package simulate
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
compose "github.com/compose-spec/compose-go/types"
"github.com/otiai10/copy"
"gopkg.in/yaml.v3"
)
const (
BOOSTRAP_SERVICE_NAME = "bootstrap"
PUBLISHER_SERVICE_NAME = "waku-publisher"
CONTAINER_ENTRYPOINT = "/opt/run.sh"
)
type Simulation struct {
Name string
TargetDir string
Manifest Manifest
}
func NewSimulation(name string, targetDir string) *Simulation {
if targetDir == "" {
targetDir = name
}
return &Simulation{
Name: name,
TargetDir: targetDir,
}
}
func (s *Simulation) Load(path string) error {
bytes, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var m = Manifest{}
err = yaml.Unmarshal(bytes, &m)
if err != nil {
return err
}
if m.Bootstrap == nil {
b := true
m.Bootstrap = &b
}
if m.Monitoring == nil {
b := true
m.Monitoring = &b
}
for i, g := range m.Groups {
if g.Name == "" {
return fmt.Errorf("Missing name for group %d", i)
}
}
s.Manifest = m
return nil
}
func (s *Simulation) Generate() error {
c := compose.Project{}
bootstrap := false
if *s.Manifest.Bootstrap {
service, err := s.prepBootstrapNode()
if err != nil {
return err
}
c.Services = append(c.Services, service)
bootstrap = true
}
for _, g := range s.Manifest.Groups {
for j := 0; j < g.Count; j++ {
service := compose.ServiceConfig{}
service.Image = g.Image
service.Name = fmt.Sprintf("%s-%d", g.Name, j)
service.Environment = g.Env
if bootstrap {
service.DependsOn = compose.DependsOnConfig{
BOOSTRAP_SERVICE_NAME: compose.ServiceDependency{
Condition: compose.ServiceConditionStarted,
},
}
}
s.ensureRunScript(g, &service)
service.Command = append(service.Command, g.Args...)
s.ensureVolumes(g, &service)
c.Services = append(c.Services, service)
}
}
if *s.Manifest.Monitoring {
services := c.ServiceNames()
monitoringServices, err := s.prepMonitoring(s.TargetDir, services)
if err != nil {
return err
}
c.Services = append(c.Services, monitoringServices...)
}
if s.Manifest.Publisher != nil {
service, err := s.prepPublisher()
if err != nil {
return err
}
c.Services = append(c.Services, service)
}
log.Printf("Generating Docker Compose environment into the folder ./%s", s.TargetDir)
bytes, err := c.MarshalYAML()
if err != nil {
return err
}
err = os.MkdirAll(s.TargetDir, 0755)
if err != nil {
return err
}
ioutil.WriteFile(path.Join(s.TargetDir, "compose.yaml"), bytes, 0644)
return nil
}
func (s *Simulation) ensureRunScript(g Group, service *compose.ServiceConfig) error {
command := ""
switch g.Typ {
case "nwaku":
command = "run_nwaku.sh"
case "gowaku":
command = "run_gowaku.sh"
case "wakupublisher":
command = "run_wakupublisher.sh"
default:
return fmt.Errorf("Unknown service type: %s", g.Typ)
}
if service.Command == nil || len(service.Command) == 0 {
service.Command = compose.ShellCommand{CONTAINER_ENTRYPOINT}
} else {
service.Command = append(compose.ShellCommand{CONTAINER_ENTRYPOINT}, service.Command...)
}
if service.Volumes == nil {
service.Volumes = make([]compose.ServiceVolumeConfig, 0)
}
service.Volumes = append(service.Volumes, compose.ServiceVolumeConfig{
Type: compose.VolumeTypeBind,
Source: fmt.Sprintf("../%s", command),
Target: CONTAINER_ENTRYPOINT,
ReadOnly: true,
})
service.Entrypoint = compose.ShellCommand{"sh"}
return nil
}
func (s *Simulation) ensureVolumes(g Group, service *compose.ServiceConfig) error {
if len(g.Volumes) > 0 && service.Volumes == nil {
service.Volumes = make([]compose.ServiceVolumeConfig, 0)
}
for _, v := range g.Volumes {
src := v.Src
if v.PerNode && !v.ReadOnly {
src = fmt.Sprintf("%s-%s", v.Src, service.Name)
_, err := os.Stat(v.Src) //TODO: Is there a difference between dir and file?
if err != nil && !os.IsNotExist(err) {
return err
}
if os.IsNotExist(err) {
err = os.MkdirAll(path.Join(s.TargetDir, src), 0755)
if err != nil {
return err
}
} else {
err = copy.Copy(v.Src, path.Join(s.TargetDir, src))
if err != nil {
return err
}
}
}
volume := compose.ServiceVolumeConfig{
Type: compose.VolumeTypeBind,
Source: src,
Target: v.Dst,
ReadOnly: v.ReadOnly,
}
service.Volumes = append(service.Volumes, volume)
}
return nil
}
func (s *Simulation) prepBootstrapNode() (compose.ServiceConfig, error) {
service := compose.ServiceConfig{}
service.Name = "bootstrap"
service.Image = "statusteam/nim-waku:v0.19.0"
service.Entrypoint = compose.ShellCommand{"sh"}
service.Command = compose.ShellCommand{
CONTAINER_ENTRYPOINT,
}
service.Volumes = make([]compose.ServiceVolumeConfig, 1)
service.Volumes[0] = compose.ServiceVolumeConfig{
Type: compose.VolumeTypeBind,
Source: "../run_bootstrap.sh",
Target: CONTAINER_ENTRYPOINT,
ReadOnly: true,
}
service.Ports = []compose.ServicePortConfig{
{
HostIP: "127.0.0.1",
Target: 60000,
Published: "60000",
},
{
HostIP: "127.0.0.1",
Target: 8008,
Published: "8008",
},
{
HostIP: "127.0.0.1",
Target: 9000,
Published: "9000",
},
{
HostIP: "127.0.0.1",
Target: 8545,
Published: "8545",
},
}
return service, nil
}
func (s *Simulation) prepPublisher() (compose.ServiceConfig, error) {
service := compose.ServiceConfig{}
service.Name = PUBLISHER_SERVICE_NAME
service.Image = s.Manifest.Publisher.Image
service.Entrypoint = compose.ShellCommand{"sh"}
service.Command = compose.ShellCommand{
CONTAINER_ENTRYPOINT,
}
service.Volumes = make([]compose.ServiceVolumeConfig, 1)
service.Volumes[0] = compose.ServiceVolumeConfig{
Type: compose.VolumeTypeBind,
Source: "../run_wakupublisher.sh",
Target: CONTAINER_ENTRYPOINT,
ReadOnly: true,
}
service.Environment = make(compose.MappingWithEquals)
v := fmt.Sprintf("%d", s.Manifest.Publisher.MsgPerSec)
service.Environment["MSG_PER_SECOND"] = &v
v2 := fmt.Sprintf("%d", s.Manifest.Publisher.MsgSizeKB)
service.Environment["MSG_SIZE_KBYTES"] = &v2
return service, nil
}