2023-04-08 03:46:22 +00:00
|
|
|
package beehive
|
|
|
|
|
2023-04-10 04:03:48 +00:00
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2023-05-03 05:36:43 +00:00
|
|
|
"net"
|
2023-04-16 20:01:08 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
2023-04-10 04:03:48 +00:00
|
|
|
"strconv"
|
2023-04-16 16:50:29 +00:00
|
|
|
"time"
|
2023-04-10 04:03:48 +00:00
|
|
|
)
|
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
const retryDelay = time.Second * 2
|
2023-04-08 03:46:22 +00:00
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
type WorkerConfig struct {
|
|
|
|
// Path to festoons.
|
|
|
|
FestoonsDir string
|
2023-04-08 03:46:22 +00:00
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
// Path to deployments.
|
2023-04-10 04:03:48 +00:00
|
|
|
DeploymentsDir string
|
2023-04-08 03:46:22 +00:00
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
// Address to connect to queen bee.
|
|
|
|
Queen string
|
|
|
|
|
|
|
|
// Worker IP address.
|
|
|
|
IP string
|
|
|
|
|
|
|
|
// Unique ID.
|
|
|
|
ID int
|
|
|
|
|
|
|
|
// Password.
|
|
|
|
Password string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Worker struct {
|
|
|
|
WorkerConfig
|
|
|
|
|
2023-04-08 03:46:22 +00:00
|
|
|
Deployments []*Deployment
|
2023-04-10 04:03:48 +00:00
|
|
|
|
2023-04-27 02:04:37 +00:00
|
|
|
TaskQueue chan *TaskMessage
|
2023-04-10 04:03:48 +00:00
|
|
|
|
|
|
|
requestPortsFunc func(d *Deployment) []int
|
2023-05-03 05:36:43 +00:00
|
|
|
|
|
|
|
reconnect chan struct{}
|
2023-04-10 04:03:48 +00:00
|
|
|
}
|
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
func NewWorker(config *WorkerConfig) *Worker {
|
2023-04-10 04:03:48 +00:00
|
|
|
w := &Worker{
|
2023-05-03 05:36:43 +00:00
|
|
|
WorkerConfig: *config,
|
|
|
|
TaskQueue: make(chan *TaskMessage),
|
|
|
|
reconnect: make(chan struct{}),
|
2023-04-10 04:03:48 +00:00
|
|
|
}
|
|
|
|
go w.handleTaskQueue()
|
2023-04-16 20:01:08 +00:00
|
|
|
|
|
|
|
log.Println("Loading deployments...")
|
|
|
|
// TODO list directories in deployments dir, no db needed
|
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
dirEntries, err := os.ReadDir(config.DeploymentsDir)
|
2023-04-16 20:01:08 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
for _, dirEntry := range dirEntries {
|
|
|
|
if dirEntry.IsDir() {
|
|
|
|
log.Println(dirEntry.Name())
|
2023-05-03 05:36:43 +00:00
|
|
|
d, err := LoadDeployment(path.Join(config.DeploymentsDir, dirEntry.Name()))
|
2023-04-16 20:01:08 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to load deployment %s: %s", dirEntry.Name(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Worker = w
|
|
|
|
|
|
|
|
w.Deployments = append(w.Deployments, d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Println("Finished loading deployments")
|
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
go w.handleConnect()
|
|
|
|
|
2023-04-10 04:03:48 +00:00
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2023-05-03 05:36:43 +00:00
|
|
|
func (w *Worker) handleConnect() {
|
|
|
|
log.Printf("Connecting to queen bee at %s...", w.Queen)
|
|
|
|
for {
|
|
|
|
var conn net.Conn
|
|
|
|
var err error
|
|
|
|
for {
|
|
|
|
conn, err = net.Dial("tcp", w.Queen)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Failed to connect to queen: %s", err)
|
|
|
|
log.Println("Retrying in 2 seconds...")
|
|
|
|
time.Sleep(retryDelay)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("Connected")
|
|
|
|
|
|
|
|
client := NewClient(conn)
|
|
|
|
client.Authenticate(w.ID, w.Password)
|
|
|
|
go w.HandleRead(client)
|
|
|
|
|
|
|
|
<-w.reconnect
|
|
|
|
|
|
|
|
log.Println("Reconnecting...")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-10 04:03:48 +00:00
|
|
|
func (w *Worker) handleTaskQueue() {
|
|
|
|
for t := range w.TaskQueue {
|
|
|
|
w.ExecuteTask(t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-27 02:04:37 +00:00
|
|
|
func (w *Worker) ExecuteTask(t *TaskMessage) error {
|
2023-04-10 04:03:48 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *Worker) HandleRead(c *Client) {
|
|
|
|
var readFirst bool
|
|
|
|
scanner := bufio.NewScanner(c.Conn)
|
|
|
|
for scanner.Scan() {
|
2023-04-15 05:20:08 +00:00
|
|
|
log.Printf(" <- %s", scanner.Bytes())
|
|
|
|
|
2023-04-10 04:03:48 +00:00
|
|
|
if !readFirst {
|
|
|
|
if !bytes.Equal(scanner.Bytes(), []byte(serverWelcomeMessage)) {
|
|
|
|
log.Fatalf("unexpected server reply: %s", scanner.Bytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
readFirst = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-04-27 02:04:37 +00:00
|
|
|
task := &TaskMessage{}
|
2023-04-10 04:03:48 +00:00
|
|
|
err := json.Unmarshal(scanner.Bytes(), task)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to unmarshal %s: %s", scanner.Bytes(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf(" <- task: %+v", task)
|
|
|
|
|
|
|
|
switch task.Type {
|
|
|
|
case TaskHealth:
|
2023-04-15 05:20:08 +00:00
|
|
|
result := NewResult(TaskHealth, map[string]string{
|
2023-04-10 04:03:48 +00:00
|
|
|
"time": task.Parameters["time"],
|
|
|
|
})
|
2023-04-15 05:20:08 +00:00
|
|
|
|
|
|
|
for _, d := range w.Deployments {
|
2023-04-16 16:50:29 +00:00
|
|
|
eventsJson, err := json.Marshal(d.Events)
|
2023-04-15 05:20:08 +00:00
|
|
|
if err != nil {
|
2023-04-16 16:50:29 +00:00
|
|
|
log.Fatal(err)
|
2023-04-15 05:20:08 +00:00
|
|
|
}
|
|
|
|
|
2023-04-16 16:50:29 +00:00
|
|
|
result.Parameters[fmt.Sprintf("events_%d", d.ID)] = string(eventsJson)
|
|
|
|
|
|
|
|
d.Events = d.Events[:0]
|
|
|
|
// TODO deployment mutex
|
2023-04-15 05:20:08 +00:00
|
|
|
}
|
|
|
|
|
2023-04-10 04:03:48 +00:00
|
|
|
resultJson, err := json.Marshal(result)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to marshal result %+v: %s", result, err)
|
|
|
|
}
|
|
|
|
c.Out <- append(resultJson, '\n')
|
|
|
|
case TaskDeploy:
|
|
|
|
id, err := strconv.Atoi(task.Parameters["id"])
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to parse id: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := strconv.Atoi(task.Parameters["client"])
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to parse client: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
ports := make([]int, 10)
|
|
|
|
for i := range ports {
|
|
|
|
ports[i], err = strconv.Atoi(task.Parameters[fmt.Sprintf("port_%d", i)])
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to parse port %d: %s", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-16 20:01:08 +00:00
|
|
|
var d *Deployment
|
|
|
|
var newDeployment bool
|
|
|
|
for _, deployment := range w.Deployments {
|
|
|
|
if id == deployment.ID {
|
|
|
|
d = deployment
|
|
|
|
d.Client = client
|
|
|
|
d.Festoon = task.Parameters["festoon"]
|
|
|
|
d.Ports = ports
|
|
|
|
break
|
|
|
|
}
|
2023-04-10 04:03:48 +00:00
|
|
|
}
|
2023-04-16 20:01:08 +00:00
|
|
|
if d == nil {
|
|
|
|
d = &Deployment{
|
|
|
|
ID: id,
|
|
|
|
Client: client,
|
|
|
|
Festoon: task.Parameters["festoon"],
|
|
|
|
UID: 7777,
|
|
|
|
Ports: ports,
|
|
|
|
Worker: w,
|
|
|
|
}
|
2023-04-10 04:03:48 +00:00
|
|
|
|
2023-04-16 20:01:08 +00:00
|
|
|
newDeployment = true
|
|
|
|
|
|
|
|
go d.handleEvents()
|
|
|
|
time.Sleep(10 * time.Millisecond) // Give events handler some time to attach.
|
|
|
|
}
|
2023-04-16 16:50:29 +00:00
|
|
|
|
2023-04-10 04:03:48 +00:00
|
|
|
err = d.deploy()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to deploy %+v: %s", d, err)
|
|
|
|
}
|
|
|
|
|
2023-04-16 20:01:08 +00:00
|
|
|
if newDeployment {
|
|
|
|
w.Deployments = append(w.Deployments, d)
|
|
|
|
}
|
2023-04-15 05:20:08 +00:00
|
|
|
|
2023-04-10 04:03:48 +00:00
|
|
|
// Send result
|
2023-04-15 05:20:08 +00:00
|
|
|
result := NewResult(TaskDeploy, map[string]string{
|
2023-04-10 04:03:48 +00:00
|
|
|
"id": task.Parameters["id"],
|
|
|
|
"status": "ok",
|
|
|
|
})
|
|
|
|
resultJson, err := json.Marshal(result)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to marshal result %+v: %s", result, err)
|
|
|
|
}
|
|
|
|
c.Out <- append(resultJson, '\n')
|
|
|
|
default:
|
|
|
|
log.Fatalf("unknown task type %d", task.Type)
|
|
|
|
}
|
|
|
|
}
|
2023-05-03 05:36:43 +00:00
|
|
|
|
|
|
|
log.Println("Connection lost")
|
|
|
|
|
|
|
|
w.reconnect <- struct{}{}
|
2023-04-08 03:46:22 +00:00
|
|
|
}
|