Skip to content

Commit ac66d9b

Browse files
committed
feat(agent): dns based service discovery and volume manager added
1 parent 78fa6ce commit ac66d9b

18 files changed

+1647
-0
lines changed

agent/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.vscode
2+
docs
3+
agent.db
4+
agent.db-*
5+
docs.txt
6+
agent

agent/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Container
2+
- Create Container
3+
-
4+
5+
Service
6+
- Create Service
7+
- Get Service
8+
- Stop Service
9+
- Remove Service
10+
- Update Service
11+
12+
DNS
13+
- Create Entry
14+
- Delete Entry
15+
- Get Entry
16+
17+
IPTables
18+
-
19+
20+
21+
22+
----- Important ---------
23+
User: root
24+
Directories -
25+
- /var/lib/swiftwave > Main directory to store swiftwave persistent files
26+
- /var/lib/swiftwave/agent > Directory to store agent related files
27+
- Will be kinda work directory for service, no need to specify anywhere

agent/api_server.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/labstack/echo/v4"
8+
"github.com/labstack/echo/v4/middleware"
9+
)
10+
11+
func startHttpServer() {
12+
e := echo.New()
13+
e.HideBanner = true
14+
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
15+
Format: "method=${method}, uri=${uri}, status=${status}\n",
16+
}))
17+
18+
// Auth middleware
19+
// e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
20+
// return func(c echo.Context) error {
21+
// token := c.Request().Header.Get("Authorization")
22+
// if token != "Token" {
23+
// return c.JSON(http.StatusUnauthorized, "Unauthorized")
24+
// }
25+
// return next(c)
26+
// }
27+
// })
28+
29+
// Container API
30+
31+
// Create + Remove + Status + Logs
32+
// Container is mutable
33+
// For update, swiftwave will remove previous container and create a new one
34+
35+
/*
36+
For zero downtime deployment, we can use the following steps:
37+
1. Create a new container with the new image
38+
2. Wait for the new container to be ready
39+
3. Update the DNS record to point to the new container
40+
4. Remove the old DNS records
41+
5. Remove the old containers
42+
*/
43+
44+
// Volume API
45+
e.GET("/volumes", fetchAllVolumes)
46+
e.GET("/volumes/:uuid", fetchVolume)
47+
e.POST("/volumes/:uuid/size", fetchVolumeSize)
48+
e.POST("/volumes", createVolume)
49+
e.DELETE("/volumes/:uuid", deleteVolume)
50+
51+
// DNS Record API
52+
e.GET("/dns", fetchAllDNSRecords)
53+
e.GET("/dns/:domain", fetchDNSRecordsByDomain)
54+
e.DELETE("/dns", deleteDNSRecord)
55+
e.POST("/dns", createDNSRecord)
56+
57+
// Wireguard Peer API
58+
59+
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
60+
e.Logger.Fatal(err)
61+
}
62+
}

agent/db_client.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"os"
7+
"runtime"
8+
9+
"gorm.io/driver/sqlite"
10+
"gorm.io/gorm"
11+
"gorm.io/gorm/logger"
12+
)
13+
14+
var (
15+
rDB *gorm.DB // rDB is the read-only database instance
16+
rwDB *gorm.DB // rwDB is the read-write database instance
17+
)
18+
19+
const dbName = "agent.db"
20+
21+
func CreateDatabaseFileIfNotExist() error {
22+
if _, err := os.Stat(dbName); os.IsNotExist(err) {
23+
file, err := os.Create(dbName)
24+
if err != nil {
25+
return fmt.Errorf("failed to create database file: %v", err)
26+
}
27+
file.Close()
28+
}
29+
return nil
30+
}
31+
32+
func InitiateDatabaseInstances() error {
33+
readDBInstance, err := OpenSqliteDatabase(dbName, true)
34+
if err != nil {
35+
return fmt.Errorf("failed to open read-only database: %v", err)
36+
}
37+
rDB = readDBInstance
38+
readWriteDBInstance, err := OpenSqliteDatabase(dbName, false)
39+
if err != nil {
40+
return fmt.Errorf("failed to open read-write database: %v", err)
41+
}
42+
rwDB = readWriteDBInstance
43+
return nil
44+
}
45+
46+
func MigrateDatabase() error {
47+
if rwDB == nil {
48+
return fmt.Errorf("read-write database instance is nil or not initialized")
49+
}
50+
err := rwDB.AutoMigrate(&DNSEntry{}, &Volume{}, &VolumeMount{}, &EnvironmentVariable{}, &Container{})
51+
if err != nil {
52+
return fmt.Errorf("failed to migrate Agent table: %v", err)
53+
}
54+
return nil
55+
}
56+
57+
func OpenSqliteDatabase(file string, readonly bool) (*gorm.DB, error) {
58+
dbString := SQLiteDbString(file, readonly)
59+
gormDb, err := gorm.Open(sqlite.Open(dbString), &gorm.Config{
60+
Logger: logger.Default.LogMode(logger.Info),
61+
})
62+
if err != nil {
63+
return nil, err
64+
}
65+
db, err := gormDb.DB()
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
pragmasToSet := []string{
71+
"temp_store=memory",
72+
}
73+
for _, pragma := range pragmasToSet {
74+
_, err = db.Exec("PRAGMA " + pragma + ";")
75+
if err != nil {
76+
return nil, err
77+
}
78+
}
79+
if readonly {
80+
db.SetMaxOpenConns(max(4, runtime.NumCPU()))
81+
} else {
82+
db.SetMaxOpenConns(1)
83+
}
84+
85+
return gormDb, nil
86+
}
87+
88+
func SQLiteDbString(file string, readonly bool) string {
89+
connectionParams := make(url.Values)
90+
connectionParams.Add("_busy_timeout", "5000")
91+
connectionParams.Add("_synchronous", "NORMAL")
92+
connectionParams.Add("_cache_size", "-20000")
93+
connectionParams.Add("_foreign_keys", "true")
94+
if readonly {
95+
connectionParams.Add("mode", "ro")
96+
} else {
97+
connectionParams.Add("_journal_mode", "WAL")
98+
connectionParams.Add("_txlock", "IMMEDIATE")
99+
connectionParams.Add("mode", "rwc")
100+
}
101+
102+
return "file:" + file + "?" + connectionParams.Encode()
103+
}

agent/db_schema.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package main
2+
3+
type ContainerStatus string
4+
5+
const (
6+
ContainerStatusImagePulling ContainerStatus = "image_pulling"
7+
ContainerStatusImagePullErr ContainerStatus = "image_pull_error"
8+
ContainerStatusPending ContainerStatus = "pending"
9+
ContainerStatusRunning ContainerStatus = "running"
10+
ContainerStatusStopped ContainerStatus = "stopped"
11+
ContainerStatusExited ContainerStatus = "exited"
12+
)
13+
14+
type Container struct {
15+
Name string `gorm:"column:name;index"`
16+
ServiceName string `gorm:"column:service_name;index"`
17+
Image string `gorm:"column:image;index"`
18+
IPAddress string `gorm:"column:ip_address"`
19+
Status ContainerStatus `gorm:"column:status;default:pending"`
20+
IsHealthy bool `gorm:"column:is_healthy;default:false"`
21+
ExitReason string `gorm:"column:exit_reason"`
22+
Entrypoint string `gorm:"column:entrypoint"`
23+
Command string `gorm:"column:command;default:[]"`
24+
NoNewPrivileges bool `gorm:"column:no_new_privileges;default:true"`
25+
AddCapabilities string `gorm:"column:add_capabilities"`
26+
MemorySoftLimitMB int64 `gorm:"column:memory_soft_limit_mb"`
27+
MemoryHardLimitMB int64 `gorm:"column:memory_hard_limit_mb"`
28+
}
29+
30+
type EnvironmentVariable struct {
31+
Name string `gorm:"column:name"`
32+
Value string `gorm:"column:value"`
33+
ContainerName string `gorm:"column:container_name;index"`
34+
}
35+
36+
type VolumeMount struct {
37+
MountPath string `gorm:"column:mount_path"`
38+
VolumeUUID string `gorm:"column:volume_uuid;index"`
39+
ContainerName string `gorm:"column:container_name;index"`
40+
}
41+
42+
type VolumeType string
43+
44+
const (
45+
LocalVolume VolumeType = "local"
46+
NFSVolume VolumeType = "nfs"
47+
CIFSVolume VolumeType = "cifs"
48+
)
49+
50+
type Volume struct {
51+
UUID string `gorm:"column:uuid;primaryKey"`
52+
Type VolumeType `gorm:"column:type;index"`
53+
LocalConfig LocalVolumeConfig `gorm:"embedded;embeddedPrefix:local_config_"`
54+
NFSConfig NFSVolumeConfig `gorm:"embedded;embeddedPrefix:nfs_config_"`
55+
CIFSConfig CIFSVolumeConfig `gorm:"embedded;embeddedPrefix:cifs_config_"`
56+
}
57+
58+
type LocalVolumeConfig struct {
59+
IsCustomPath bool `gorm:"column:is_custom_path"`
60+
CustomPath string `gorm:"column:custom_path"`
61+
}
62+
63+
type NFSVolumeConfig struct {
64+
Host string `gorm:"column:host"`
65+
Path string `gorm:"column:path"`
66+
Version int `gorm:"column:version"`
67+
}
68+
69+
type CIFSVolumeConfig struct {
70+
Share string `gorm:"column:share"`
71+
Host string `gorm:"column:host"`
72+
Username string `gorm:"column:username"`
73+
Password string `gorm:"column:password"`
74+
FileMode string `gorm:"column:file_mode"`
75+
DirMode string `gorm:"column:dir_mode"`
76+
Uid int `gorm:"column:uid;default:0"`
77+
Gid int `gorm:"column:gid;default:0"`
78+
}
79+
80+
type DNSEntry struct {
81+
Domain string `gorm:"column:domain;index" json:"domain"`
82+
IP string `gorm:"column:ip;index" json:"ip"`
83+
}

agent/dns_api.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/labstack/echo/v4"
7+
)
8+
9+
func createDNSRecord(c echo.Context) error {
10+
var dnsEntry DNSEntry
11+
if err := c.Bind(&dnsEntry); err != nil {
12+
return c.JSON(http.StatusBadRequest, Response{
13+
Message: "Invalid request",
14+
Error: err.Error(),
15+
})
16+
}
17+
if err := dnsEntry.Create(); err != nil {
18+
return c.JSON(http.StatusInternalServerError, Response{
19+
Message: "Failed to create DNS record",
20+
Error: err.Error(),
21+
})
22+
}
23+
return c.JSON(http.StatusOK, Response{
24+
Message: "DNS record created successfully",
25+
Data: dnsEntry,
26+
})
27+
}
28+
29+
func deleteDNSRecord(c echo.Context) error {
30+
var dnsEntry DNSEntry
31+
if err := c.Bind(&dnsEntry); err != nil {
32+
return c.JSON(http.StatusBadRequest, Response{
33+
Message: "Invalid request",
34+
Error: err.Error(),
35+
})
36+
}
37+
if err := dnsEntry.Delete(); err != nil {
38+
return c.JSON(http.StatusInternalServerError, Response{
39+
Message: "Failed to delete DNS record",
40+
Error: err.Error(),
41+
})
42+
}
43+
return c.JSON(http.StatusOK, Response{
44+
Message: "DNS record deleted successfully",
45+
Data: dnsEntry,
46+
})
47+
}
48+
49+
func fetchAllDNSRecords(c echo.Context) error {
50+
dnsEntries, err := FetchAllDNSRecords()
51+
if err != nil {
52+
return c.JSON(http.StatusInternalServerError, Response{
53+
Message: "Failed to fetch DNS records",
54+
Error: err.Error(),
55+
})
56+
}
57+
return c.JSON(http.StatusOK, Response{
58+
Message: "DNS records fetched successfully",
59+
Data: dnsEntries,
60+
})
61+
}
62+
63+
func fetchDNSRecordsByDomain(c echo.Context) error {
64+
domain := c.Param("domain")
65+
dnsEntries, err := FetchDNSRecordsByDomain(domain)
66+
if err != nil {
67+
return c.JSON(http.StatusInternalServerError, Response{
68+
Message: "Failed to fetch DNS records",
69+
Error: err.Error(),
70+
})
71+
}
72+
return c.JSON(http.StatusOK, Response{
73+
Message: "DNS records fetched successfully",
74+
Data: dnsEntries,
75+
})
76+
}

0 commit comments

Comments
 (0)