mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-06 05:52:19 +08:00
feat: split rate limits with configurable per-endpoint controls
This commit is contained in:
@@ -2,6 +2,8 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -210,10 +212,21 @@ func listContainers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func createContainer(w http.ResponseWriter, r *http.Request) {
|
||||
var cfg lxc.ContainerConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
|
||||
return
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(body, &cfg); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
|
||||
return
|
||||
}
|
||||
_ = json.Unmarshal(body, &fields)
|
||||
if err := normalizeCreateResourceLimits(&cfg, fields); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
if cfg.Name == "" {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Container name is required"})
|
||||
return
|
||||
@@ -386,10 +399,14 @@ func updateTrafficLimit(w http.ResponseWriter, r *http.Request, id int) {
|
||||
|
||||
func updateResourceLimit(w http.ResponseWriter, r *http.Request, id int) {
|
||||
var req struct {
|
||||
VCPU float64 `json:"vcpu"`
|
||||
RAMMB int `json:"ram_mb"`
|
||||
IOMBps int `json:"io_speed_mbps"`
|
||||
BWMbps int `json:"network_bw_mbps"`
|
||||
VCPU *float64 `json:"vcpu"`
|
||||
RAMMB *int `json:"ram_mb"`
|
||||
IOMBps *int `json:"io_speed_mbps"`
|
||||
IOReadMBps *int `json:"io_read_mbps"`
|
||||
IOWriteMBps *int `json:"io_write_mbps"`
|
||||
BWMbps *int `json:"network_bw_mbps"`
|
||||
NetworkDownMbps *int `json:"network_down_mbps"`
|
||||
NetworkUpMbps *int `json:"network_up_mbps"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request"})
|
||||
@@ -404,21 +421,35 @@ func updateResourceLimit(w http.ResponseWriter, r *http.Request, id int) {
|
||||
// Update config
|
||||
nextVCPU := c.VCPU
|
||||
nextRAMMB := c.RAMMB
|
||||
if req.VCPU > 0 {
|
||||
nextVCPU = req.VCPU
|
||||
if req.VCPU != nil {
|
||||
nextVCPU = *req.VCPU
|
||||
}
|
||||
if req.RAMMB > 0 {
|
||||
nextRAMMB = req.RAMMB
|
||||
if req.RAMMB != nil {
|
||||
nextRAMMB = *req.RAMMB
|
||||
}
|
||||
if err := validateRuntimeResourceRequest(c.Runtime(), nextVCPU, nextRAMMB, c.DiskGB); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
for name, value := range map[string]*int{
|
||||
"network_bw_mbps": req.BWMbps,
|
||||
"network_down_mbps": req.NetworkDownMbps,
|
||||
"network_up_mbps": req.NetworkUpMbps,
|
||||
"io_speed_mbps": req.IOMBps,
|
||||
"io_read_mbps": req.IOReadMBps,
|
||||
"io_write_mbps": req.IOWriteMBps,
|
||||
} {
|
||||
if err := rejectNegativeLimit(name, value); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.VCPU = nextVCPU
|
||||
c.RAMMB = nextRAMMB
|
||||
c.IOSpeedMBps = req.IOMBps
|
||||
c.NetworkBWMbps = req.BWMbps
|
||||
applyNetworkLimitPatch(c, req.BWMbps, req.NetworkDownMbps, req.NetworkUpMbps)
|
||||
applyIOLimitPatch(c, req.IOMBps, req.IOReadMBps, req.IOWriteMBps)
|
||||
config.NormalizeContainerResourceAliases(c)
|
||||
config.SaveConfig()
|
||||
|
||||
// Re-apply resource limits to running container
|
||||
@@ -436,6 +467,114 @@ func updateResourceLimit(w http.ResponseWriter, r *http.Request, id int) {
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Message: msg})
|
||||
}
|
||||
|
||||
func normalizeCreateResourceLimits(cfg *lxc.ContainerConfig, fields map[string]json.RawMessage) error {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
if err := rejectNegativeCreateLimits(*cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
bwSet := hasJSONField(fields, "network_bw_mbps")
|
||||
downSet := hasJSONField(fields, "network_down_mbps")
|
||||
upSet := hasJSONField(fields, "network_up_mbps")
|
||||
if bwSet {
|
||||
if !downSet {
|
||||
cfg.NetworkDownMbps = cfg.NetworkBWMbps
|
||||
}
|
||||
if !upSet {
|
||||
cfg.NetworkUpMbps = cfg.NetworkBWMbps
|
||||
}
|
||||
}
|
||||
ioSet := hasJSONField(fields, "io_speed_mbps")
|
||||
readSet := hasJSONField(fields, "io_read_mbps")
|
||||
writeSet := hasJSONField(fields, "io_write_mbps")
|
||||
if ioSet {
|
||||
if !readSet {
|
||||
cfg.IOReadMBps = cfg.IOSpeedMBps
|
||||
}
|
||||
if !writeSet {
|
||||
cfg.IOWriteMBps = cfg.IOSpeedMBps
|
||||
}
|
||||
}
|
||||
cfg.NormalizeResourceAliases()
|
||||
return nil
|
||||
}
|
||||
|
||||
func rejectNegativeCreateLimits(cfg lxc.ContainerConfig) error {
|
||||
for name, value := range map[string]int{
|
||||
"network_bw_mbps": cfg.NetworkBWMbps,
|
||||
"network_down_mbps": cfg.NetworkDownMbps,
|
||||
"network_up_mbps": cfg.NetworkUpMbps,
|
||||
"io_speed_mbps": cfg.IOSpeedMBps,
|
||||
"io_read_mbps": cfg.IOReadMBps,
|
||||
"io_write_mbps": cfg.IOWriteMBps,
|
||||
} {
|
||||
if value < 0 {
|
||||
return fmt.Errorf("%s cannot be negative", name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasJSONField(fields map[string]json.RawMessage, name string) bool {
|
||||
if fields == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := fields[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func rejectNegativeLimit(name string, value *int) error {
|
||||
if value != nil && *value < 0 {
|
||||
return fmt.Errorf("%s cannot be negative", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyNetworkLimitPatch(c *config.Container, legacy *int, down *int, up *int) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
config.NormalizeContainerResourceAliases(c)
|
||||
nextDown := c.NetworkDownMbps
|
||||
nextUp := c.NetworkUpMbps
|
||||
if legacy != nil {
|
||||
nextDown = *legacy
|
||||
nextUp = *legacy
|
||||
}
|
||||
if down != nil {
|
||||
nextDown = *down
|
||||
}
|
||||
if up != nil {
|
||||
nextUp = *up
|
||||
}
|
||||
c.NetworkDownMbps = nextDown
|
||||
c.NetworkUpMbps = nextUp
|
||||
c.NetworkBWMbps = config.LegacySymmetricLimit(nextDown, nextUp)
|
||||
}
|
||||
|
||||
func applyIOLimitPatch(c *config.Container, legacy *int, read *int, write *int) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
config.NormalizeContainerResourceAliases(c)
|
||||
nextRead := c.IOReadMBps
|
||||
nextWrite := c.IOWriteMBps
|
||||
if legacy != nil {
|
||||
nextRead = *legacy
|
||||
nextWrite = *legacy
|
||||
}
|
||||
if read != nil {
|
||||
nextRead = *read
|
||||
}
|
||||
if write != nil {
|
||||
nextWrite = *write
|
||||
}
|
||||
c.IOReadMBps = nextRead
|
||||
c.IOWriteMBps = nextWrite
|
||||
c.IOSpeedMBps = config.LegacySymmetricLimit(nextRead, nextWrite)
|
||||
}
|
||||
|
||||
func getRandomPort(w http.ResponseWriter, r *http.Request, id int) {
|
||||
c := config.FindContainer(id)
|
||||
if c == nil {
|
||||
|
||||
@@ -32,6 +32,7 @@ func runtimeFromTemplateID(templateID string) string {
|
||||
|
||||
func createByRuntime(cfg lxc.ContainerConfig) error {
|
||||
cfg.Virtualization = runtimeFromRequest(cfg.Virtualization)
|
||||
cfg.NormalizeResourceAliases()
|
||||
if cfg.Virtualization == config.VirtualizationKVM {
|
||||
return kvmManager.CreateContainer(cfg)
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ func (q *TaskQueue) EnqueueWithAudit(containerID int, containerName string, task
|
||||
}
|
||||
if cfg != nil {
|
||||
task.Config = *cfg
|
||||
task.Config.NormalizeResourceAliases()
|
||||
}
|
||||
q.enqueueTask(task)
|
||||
q.persistTasks()
|
||||
@@ -162,6 +163,7 @@ func (q *TaskQueue) enqueueBatchCreateList(configs []lxc.ContainerConfig, user s
|
||||
var result []string
|
||||
for _, cfg := range configs {
|
||||
cfgCopy := cfg
|
||||
cfgCopy.NormalizeResourceAliases()
|
||||
id := q.nextID
|
||||
q.nextID++
|
||||
task := &Task{
|
||||
@@ -246,6 +248,7 @@ func (q *TaskQueue) createWorker() {
|
||||
if task.Config.Name == "" {
|
||||
task.Config.Name = task.ContainerName
|
||||
}
|
||||
task.Config.NormalizeResourceAliases()
|
||||
if task.Config.Name == "" {
|
||||
task.Status = "failed"
|
||||
task.Error = "container name is required"
|
||||
@@ -592,6 +595,11 @@ func HandleBatchCreate(w http.ResponseWriter, r *http.Request) {
|
||||
if req.Containers[i].VCPU <= 0 {
|
||||
req.Containers[i].VCPU = 1
|
||||
}
|
||||
if err := rejectNegativeCreateLimits(req.Containers[i]); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: name + ": " + err.Error()})
|
||||
return
|
||||
}
|
||||
req.Containers[i].NormalizeResourceAliases()
|
||||
req.Containers[i].Virtualization = runtimeFromRequest(req.Containers[i].Virtualization)
|
||||
if req.Containers[i].RAMMB < 128 {
|
||||
req.Containers[i].RAMMB = 512
|
||||
@@ -820,6 +828,7 @@ func RestoreTasks() {
|
||||
if cfg.Name == "" {
|
||||
cfg.Name = containerName
|
||||
}
|
||||
cfg.NormalizeResourceAliases()
|
||||
containerID := st.ContainerID
|
||||
if containerID <= 0 && containerName != "" {
|
||||
if c := config.FindContainerByName(containerName); c != nil {
|
||||
|
||||
@@ -388,6 +388,7 @@ func cliCreateContainer(reader *bufio.Reader) {
|
||||
IOSpeedMBps: promptInt(reader, "IO 速度 (MB/s)", 500),
|
||||
ExtraPorts: promptPortList(reader, "额外 NAT 端口,多个用逗号分隔"),
|
||||
}
|
||||
cfg.NormalizeResourceAliases()
|
||||
|
||||
cliPrintf("\n正在创建容器 %s ...\n", name)
|
||||
if err := manager.CreateContainer(cfg); err != nil {
|
||||
|
||||
@@ -115,6 +115,8 @@ type Container struct {
|
||||
RAMMB int `json:"ram_mb"`
|
||||
DiskGB int `json:"disk_gb"`
|
||||
NetworkBWMbps int `json:"network_bw_mbps"`
|
||||
NetworkDownMbps int `json:"network_down_mbps"`
|
||||
NetworkUpMbps int `json:"network_up_mbps"`
|
||||
MonthlyTrafficGB int `json:"monthly_traffic_gb"`
|
||||
TrafficMode string `json:"traffic_mode"` // "total" or "in_out"
|
||||
TrafficInGB int `json:"traffic_in_gb"` // 0 = unlimited
|
||||
@@ -123,6 +125,8 @@ type Container struct {
|
||||
TrafficUsedTX int64 `json:"traffic_used_tx"`
|
||||
TrafficResetDate string `json:"traffic_reset_date"`
|
||||
IOSpeedMBps int `json:"io_speed_mbps"`
|
||||
IOReadMBps int `json:"io_read_mbps"`
|
||||
IOWriteMBps int `json:"io_write_mbps"`
|
||||
Status string `json:"status"`
|
||||
IP string `json:"ip"`
|
||||
PublicIPv4s []PublicIPv4Assignment `json:"public_ipv4s,omitempty"`
|
||||
@@ -704,6 +708,9 @@ func migrateLoadedConfig() bool {
|
||||
if ensureContainerNetworkAssignments() {
|
||||
changed = true
|
||||
}
|
||||
if ensureContainerResourceAliases() {
|
||||
changed = true
|
||||
}
|
||||
if ensureContainerSnapshotScheduleDefaults() {
|
||||
changed = true
|
||||
}
|
||||
@@ -802,6 +809,91 @@ func ensureContainerNetworkAssignments() bool {
|
||||
return changed
|
||||
}
|
||||
|
||||
func ensureContainerResourceAliases() bool {
|
||||
changed := false
|
||||
for i := range AppConfig.Containers {
|
||||
if NormalizeContainerResourceAliases(&AppConfig.Containers[i]) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func NormalizeContainerResourceAliases(c *Container) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
changed := false
|
||||
if c.NetworkBWMbps < 0 {
|
||||
c.NetworkBWMbps = 0
|
||||
changed = true
|
||||
}
|
||||
if c.NetworkDownMbps < 0 {
|
||||
c.NetworkDownMbps = 0
|
||||
changed = true
|
||||
}
|
||||
if c.NetworkUpMbps < 0 {
|
||||
c.NetworkUpMbps = 0
|
||||
changed = true
|
||||
}
|
||||
if c.NetworkDownMbps == 0 && c.NetworkUpMbps == 0 && c.NetworkBWMbps > 0 {
|
||||
c.NetworkDownMbps = c.NetworkBWMbps
|
||||
c.NetworkUpMbps = c.NetworkBWMbps
|
||||
changed = true
|
||||
}
|
||||
nextNetworkBW := LegacySymmetricLimit(c.NetworkDownMbps, c.NetworkUpMbps)
|
||||
if c.NetworkBWMbps != nextNetworkBW {
|
||||
c.NetworkBWMbps = nextNetworkBW
|
||||
changed = true
|
||||
}
|
||||
|
||||
if c.IOSpeedMBps < 0 {
|
||||
c.IOSpeedMBps = 0
|
||||
changed = true
|
||||
}
|
||||
if c.IOReadMBps < 0 {
|
||||
c.IOReadMBps = 0
|
||||
changed = true
|
||||
}
|
||||
if c.IOWriteMBps < 0 {
|
||||
c.IOWriteMBps = 0
|
||||
changed = true
|
||||
}
|
||||
if c.IOReadMBps == 0 && c.IOWriteMBps == 0 && c.IOSpeedMBps > 0 {
|
||||
c.IOReadMBps = c.IOSpeedMBps
|
||||
c.IOWriteMBps = c.IOSpeedMBps
|
||||
changed = true
|
||||
}
|
||||
nextIO := LegacySymmetricLimit(c.IOReadMBps, c.IOWriteMBps)
|
||||
if c.IOSpeedMBps != nextIO {
|
||||
c.IOSpeedMBps = nextIO
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func LegacySymmetricLimit(a, b int) int {
|
||||
if a < 0 {
|
||||
a = 0
|
||||
}
|
||||
if b < 0 {
|
||||
b = 0
|
||||
}
|
||||
if a == b {
|
||||
return a
|
||||
}
|
||||
if a == 0 {
|
||||
return b
|
||||
}
|
||||
if b == 0 {
|
||||
return a
|
||||
}
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func migrateSubUsers() bool {
|
||||
changed := false
|
||||
for i := range AppConfig.SubUsers {
|
||||
@@ -886,6 +978,7 @@ func AddContainer(c Container) {
|
||||
c.UUID = NewContainerUUID()
|
||||
}
|
||||
c.Virtualization = NormalizeVirtualization(c.Virtualization)
|
||||
NormalizeContainerResourceAliases(&c)
|
||||
AppConfig.Containers = append(AppConfig.Containers, c)
|
||||
SaveConfig()
|
||||
}
|
||||
|
||||
@@ -28,11 +28,15 @@ type savedTaskConfig struct {
|
||||
RAMMB int `json:"ram_mb"`
|
||||
DiskGB int `json:"disk_gb"`
|
||||
NetworkBWMbps int `json:"network_bw_mbps"`
|
||||
NetworkDownMbps int `json:"network_down_mbps"`
|
||||
NetworkUpMbps int `json:"network_up_mbps"`
|
||||
MonthlyTrafficGB int `json:"monthly_traffic_gb"`
|
||||
TrafficMode string `json:"traffic_mode"`
|
||||
TrafficInGB int `json:"traffic_in_gb"`
|
||||
TrafficOutGB int `json:"traffic_out_gb"`
|
||||
IOSpeedMBps int `json:"io_speed_mbps"`
|
||||
IOReadMBps int `json:"io_read_mbps"`
|
||||
IOWriteMBps int `json:"io_write_mbps"`
|
||||
ExtraPorts []int `json:"extra_ports"`
|
||||
PortMappingCount int `json:"port_mapping_count"`
|
||||
AssignNAT *bool `json:"assign_nat,omitempty"`
|
||||
@@ -55,10 +59,12 @@ func parseSavedTaskConfig(raw string) savedTaskConfig {
|
||||
}
|
||||
var cfg savedTaskConfig
|
||||
_ = json.Unmarshal([]byte(raw), &cfg)
|
||||
normalizeSavedTaskConfigLimits(&cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func encodeSavedTaskConfig(cfg savedTaskConfig) string {
|
||||
normalizeSavedTaskConfigLimits(&cfg)
|
||||
data, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -66,6 +72,41 @@ func encodeSavedTaskConfig(cfg savedTaskConfig) string {
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func normalizeSavedTaskConfigLimits(cfg *savedTaskConfig) {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
if cfg.NetworkBWMbps < 0 {
|
||||
cfg.NetworkBWMbps = 0
|
||||
}
|
||||
if cfg.NetworkDownMbps < 0 {
|
||||
cfg.NetworkDownMbps = 0
|
||||
}
|
||||
if cfg.NetworkUpMbps < 0 {
|
||||
cfg.NetworkUpMbps = 0
|
||||
}
|
||||
if cfg.NetworkDownMbps == 0 && cfg.NetworkUpMbps == 0 && cfg.NetworkBWMbps > 0 {
|
||||
cfg.NetworkDownMbps = cfg.NetworkBWMbps
|
||||
cfg.NetworkUpMbps = cfg.NetworkBWMbps
|
||||
}
|
||||
cfg.NetworkBWMbps = LegacySymmetricLimit(cfg.NetworkDownMbps, cfg.NetworkUpMbps)
|
||||
|
||||
if cfg.IOSpeedMBps < 0 {
|
||||
cfg.IOSpeedMBps = 0
|
||||
}
|
||||
if cfg.IOReadMBps < 0 {
|
||||
cfg.IOReadMBps = 0
|
||||
}
|
||||
if cfg.IOWriteMBps < 0 {
|
||||
cfg.IOWriteMBps = 0
|
||||
}
|
||||
if cfg.IOReadMBps == 0 && cfg.IOWriteMBps == 0 && cfg.IOSpeedMBps > 0 {
|
||||
cfg.IOReadMBps = cfg.IOSpeedMBps
|
||||
cfg.IOWriteMBps = cfg.IOSpeedMBps
|
||||
}
|
||||
cfg.IOSpeedMBps = LegacySymmetricLimit(cfg.IOReadMBps, cfg.IOWriteMBps)
|
||||
}
|
||||
|
||||
func encodeStringSlice(values []string) string {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
@@ -148,6 +189,8 @@ func ensureSchema() error {
|
||||
ram_mb INTEGER,
|
||||
disk_gb INTEGER,
|
||||
network_bw_mbps INTEGER,
|
||||
network_down_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
network_up_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
monthly_traffic_gb INTEGER,
|
||||
traffic_mode TEXT,
|
||||
traffic_in_gb INTEGER,
|
||||
@@ -156,6 +199,8 @@ func ensureSchema() error {
|
||||
traffic_used_tx INTEGER,
|
||||
traffic_reset_date TEXT,
|
||||
io_speed_mbps INTEGER,
|
||||
io_read_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
io_write_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
status TEXT,
|
||||
ip TEXT,
|
||||
ipv6 TEXT,
|
||||
@@ -282,11 +327,15 @@ func ensureSchema() error {
|
||||
cfg_ram_mb INTEGER,
|
||||
cfg_disk_gb INTEGER,
|
||||
cfg_network_bw_mbps INTEGER,
|
||||
cfg_network_down_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
cfg_network_up_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
cfg_monthly_traffic_gb INTEGER,
|
||||
cfg_traffic_mode TEXT,
|
||||
cfg_traffic_in_gb INTEGER,
|
||||
cfg_traffic_out_gb INTEGER,
|
||||
cfg_io_speed_mbps INTEGER,
|
||||
cfg_io_read_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
cfg_io_write_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
cfg_port_mapping_count INTEGER,
|
||||
cfg_assign_nat INTEGER,
|
||||
cfg_snapshot_limit INTEGER,
|
||||
@@ -340,6 +389,7 @@ func ensureSchema() error {
|
||||
}
|
||||
|
||||
func ensureSchemaMigrations() error {
|
||||
added := map[string]bool{}
|
||||
for _, column := range []struct {
|
||||
table string
|
||||
name string
|
||||
@@ -352,6 +402,10 @@ func ensureSchemaMigrations() error {
|
||||
{"api_keys", "last_used_ip", "TEXT"},
|
||||
{"tasks", "ip", "TEXT"},
|
||||
{"tasks", "user_agent", "TEXT"},
|
||||
{"tasks", "cfg_network_down_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"tasks", "cfg_network_up_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"tasks", "cfg_io_read_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"tasks", "cfg_io_write_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"tasks", "cfg_assign_ipv4", "INTEGER"},
|
||||
{"tasks", "cfg_ipv4_count", "INTEGER"},
|
||||
{"tasks", "cfg_public_ipv4s", "TEXT"},
|
||||
@@ -364,21 +418,61 @@ func ensureSchemaMigrations() error {
|
||||
{"port_mappings", "host_ip", "TEXT"},
|
||||
{"container_public_ipv4s", "prefix_len", "INTEGER"},
|
||||
{"container_public_ipv4s", "gateway", "TEXT"},
|
||||
{"containers", "network_down_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"containers", "network_up_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"containers", "io_read_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"containers", "io_write_mbps", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"containers", "firewall_enabled", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"containers", "firewall_default_action", "TEXT NOT NULL DEFAULT 'DROP'"},
|
||||
{"containers", "firewall_rules", "TEXT"},
|
||||
} {
|
||||
if err := ensureColumn(column.table, column.name, column.def); err != nil {
|
||||
wasAdded, err := ensureColumn(column.table, column.name, column.def)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wasAdded {
|
||||
added[column.table+"."+column.name] = true
|
||||
}
|
||||
}
|
||||
if added["containers.network_down_mbps"] || added["containers.network_up_mbps"] {
|
||||
if _, err := db.Exec(`UPDATE containers
|
||||
SET network_down_mbps = COALESCE(NULLIF(network_down_mbps, 0), COALESCE(network_bw_mbps, 0)),
|
||||
network_up_mbps = COALESCE(NULLIF(network_up_mbps, 0), COALESCE(network_bw_mbps, 0))
|
||||
WHERE COALESCE(network_bw_mbps, 0) > 0`); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if added["containers.io_read_mbps"] || added["containers.io_write_mbps"] {
|
||||
if _, err := db.Exec(`UPDATE containers
|
||||
SET io_read_mbps = COALESCE(NULLIF(io_read_mbps, 0), COALESCE(io_speed_mbps, 0)),
|
||||
io_write_mbps = COALESCE(NULLIF(io_write_mbps, 0), COALESCE(io_speed_mbps, 0))
|
||||
WHERE COALESCE(io_speed_mbps, 0) > 0`); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if added["tasks.cfg_network_down_mbps"] || added["tasks.cfg_network_up_mbps"] {
|
||||
if _, err := db.Exec(`UPDATE tasks
|
||||
SET cfg_network_down_mbps = COALESCE(NULLIF(cfg_network_down_mbps, 0), COALESCE(cfg_network_bw_mbps, 0)),
|
||||
cfg_network_up_mbps = COALESCE(NULLIF(cfg_network_up_mbps, 0), COALESCE(cfg_network_bw_mbps, 0))
|
||||
WHERE COALESCE(cfg_network_bw_mbps, 0) > 0`); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if added["tasks.cfg_io_read_mbps"] || added["tasks.cfg_io_write_mbps"] {
|
||||
if _, err := db.Exec(`UPDATE tasks
|
||||
SET cfg_io_read_mbps = COALESCE(NULLIF(cfg_io_read_mbps, 0), COALESCE(cfg_io_speed_mbps, 0)),
|
||||
cfg_io_write_mbps = COALESCE(NULLIF(cfg_io_write_mbps, 0), COALESCE(cfg_io_speed_mbps, 0))
|
||||
WHERE COALESCE(cfg_io_speed_mbps, 0) > 0`); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureColumn(table, name, def string) error {
|
||||
func ensureColumn(table, name, def string) (bool, error) {
|
||||
rows, err := db.Query("PRAGMA table_info(" + table + ")")
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
@@ -387,17 +481,17 @@ func ensureColumn(table, name, def string) error {
|
||||
var notNull, pk int
|
||||
var defaultValue interface{}
|
||||
if err := rows.Scan(&cid, &columnName, &columnType, ¬Null, &defaultValue, &pk); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if columnName == name {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
_, err = db.Exec("ALTER TABLE " + table + " ADD COLUMN " + name + " " + def)
|
||||
return err
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func loadConfigFromDB() (*ClicdConfig, bool, error) {
|
||||
@@ -578,20 +672,25 @@ func saveMeta(tx *sql.Tx) error {
|
||||
|
||||
func saveContainers(tx *sql.Tx) error {
|
||||
for _, c := range AppConfig.Containers {
|
||||
NormalizeContainerResourceAliases(&c)
|
||||
if _, err := tx.Exec(`INSERT INTO containers (
|
||||
id, uuid, name, virtualization, lxc_name, kvm_name, disk_image, mac_address, template,
|
||||
vcpu, ram_mb, disk_gb, network_bw_mbps, monthly_traffic_gb, traffic_mode, traffic_in_gb,
|
||||
traffic_out_gb, traffic_used_rx, traffic_used_tx, traffic_reset_date, io_speed_mbps,
|
||||
vcpu, ram_mb, disk_gb, network_bw_mbps, network_down_mbps, network_up_mbps,
|
||||
monthly_traffic_gb, traffic_mode, traffic_in_gb,
|
||||
traffic_out_gb, traffic_used_rx, traffic_used_tx, traffic_reset_date,
|
||||
io_speed_mbps, io_read_mbps, io_write_mbps,
|
||||
status, ip, ipv6, ipv6_prefix_len, ipv6_interface, vnc_port, ssh_port, ssh_password,
|
||||
ssh_host_key, port_mapping_limit, snapshot_limit, created_at, expires_at,
|
||||
snapshot_schedule_enabled, snapshot_schedule_interval_hours, snapshot_schedule_time,
|
||||
snapshot_schedule_last_run, snapshot_schedule_next_run, snapshot_schedule_created_by,
|
||||
policy_blocked, policy_blocked_reason, policy_blocked_at,
|
||||
firewall_enabled, firewall_default_action, firewall_rules
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
c.ID, c.UUID, c.Name, c.Virtualization, c.LXCName, c.KVMName, c.DiskImage, c.MACAddress, c.Template,
|
||||
c.VCPU, c.RAMMB, c.DiskGB, c.NetworkBWMbps, c.MonthlyTrafficGB, c.TrafficMode, c.TrafficInGB,
|
||||
c.TrafficOutGB, c.TrafficUsedRX, c.TrafficUsedTX, c.TrafficResetDate, c.IOSpeedMBps,
|
||||
c.VCPU, c.RAMMB, c.DiskGB, c.NetworkBWMbps, c.NetworkDownMbps, c.NetworkUpMbps,
|
||||
c.MonthlyTrafficGB, c.TrafficMode, c.TrafficInGB,
|
||||
c.TrafficOutGB, c.TrafficUsedRX, c.TrafficUsedTX, c.TrafficResetDate,
|
||||
c.IOSpeedMBps, c.IOReadMBps, c.IOWriteMBps,
|
||||
c.Status, c.IP, c.IPv6, c.IPv6PrefixLen, c.IPv6Interface, c.VNCPort, c.SSHPort, c.SSHPassword,
|
||||
c.SSHHostKey, c.PortMappingLimit, c.SnapshotLimit, c.CreatedAt, c.ExpiresAt,
|
||||
boolInt(c.SnapshotScheduleEnabled), c.SnapshotScheduleIntervalHours, c.SnapshotScheduleTime,
|
||||
@@ -732,15 +831,19 @@ func saveTasksDB(tx *sql.Tx) error {
|
||||
if _, err := tx.Exec(`INSERT INTO tasks(
|
||||
id, type, container_id, container_name, status, error, created_at, template_id, user, ip, user_agent,
|
||||
cfg_name, cfg_virtualization, cfg_template_id, cfg_vcpu, cfg_cpu_percent, cfg_ram_mb, cfg_disk_gb,
|
||||
cfg_network_bw_mbps, cfg_monthly_traffic_gb, cfg_traffic_mode, cfg_traffic_in_gb,
|
||||
cfg_traffic_out_gb, cfg_io_speed_mbps, cfg_port_mapping_count, cfg_assign_nat, cfg_snapshot_limit,
|
||||
cfg_network_bw_mbps, cfg_network_down_mbps, cfg_network_up_mbps,
|
||||
cfg_monthly_traffic_gb, cfg_traffic_mode, cfg_traffic_in_gb,
|
||||
cfg_traffic_out_gb, cfg_io_speed_mbps, cfg_io_read_mbps, cfg_io_write_mbps,
|
||||
cfg_port_mapping_count, cfg_assign_nat, cfg_snapshot_limit,
|
||||
cfg_assign_ipv4, cfg_ipv4_count, cfg_public_ipv4s, cfg_assign_ipv6, cfg_ipv6_count, cfg_ipv6_addresses,
|
||||
cfg_ssh_auth_mode, cfg_ssh_password, cfg_ssh_public_key, cfg_expires_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
task.ID, task.Type, task.ContainerID, task.ContainerName, task.Status, task.Error, task.CreatedAt, task.TemplateID, task.User, task.IP, task.UserAgent,
|
||||
cfg.Name, cfg.Virtualization, cfg.TemplateID, cfg.VCPU, cfg.CPUPercent, cfg.RAMMB, cfg.DiskGB,
|
||||
cfg.NetworkBWMbps, cfg.MonthlyTrafficGB, cfg.TrafficMode, cfg.TrafficInGB,
|
||||
cfg.TrafficOutGB, cfg.IOSpeedMBps, cfg.PortMappingCount, boolPtrInt(cfg.AssignNAT), cfg.SnapshotLimit,
|
||||
cfg.NetworkBWMbps, cfg.NetworkDownMbps, cfg.NetworkUpMbps,
|
||||
cfg.MonthlyTrafficGB, cfg.TrafficMode, cfg.TrafficInGB,
|
||||
cfg.TrafficOutGB, cfg.IOSpeedMBps, cfg.IOReadMBps, cfg.IOWriteMBps,
|
||||
cfg.PortMappingCount, boolPtrInt(cfg.AssignNAT), cfg.SnapshotLimit,
|
||||
boolInt(cfg.AssignIPv4), cfg.IPv4Count, encodeStringSlice(cfg.PublicIPv4s),
|
||||
boolInt(cfg.AssignIPv6), cfg.IPv6Count, encodeStringSlice(cfg.IPv6Addresses),
|
||||
cfg.SSHAuthMode, cfg.SSHPassword, cfg.SSHPublicKey, cfg.ExpiresAt,
|
||||
@@ -788,8 +891,10 @@ func saveSnapshots(tx *sql.Tx) error {
|
||||
func loadContainers() ([]Container, error) {
|
||||
rows, err := db.Query(`SELECT
|
||||
id, uuid, name, virtualization, lxc_name, kvm_name, disk_image, mac_address, template,
|
||||
vcpu, ram_mb, disk_gb, network_bw_mbps, monthly_traffic_gb, traffic_mode, traffic_in_gb,
|
||||
traffic_out_gb, traffic_used_rx, traffic_used_tx, traffic_reset_date, io_speed_mbps,
|
||||
vcpu, ram_mb, disk_gb, network_bw_mbps, network_down_mbps, network_up_mbps,
|
||||
monthly_traffic_gb, traffic_mode, traffic_in_gb,
|
||||
traffic_out_gb, traffic_used_rx, traffic_used_tx, traffic_reset_date,
|
||||
io_speed_mbps, io_read_mbps, io_write_mbps,
|
||||
status, ip, ipv6, ipv6_prefix_len, ipv6_interface, vnc_port, ssh_port, ssh_password,
|
||||
ssh_host_key, port_mapping_limit, snapshot_limit, created_at, expires_at,
|
||||
snapshot_schedule_enabled, snapshot_schedule_interval_hours, snapshot_schedule_time,
|
||||
@@ -810,8 +915,10 @@ func loadContainers() ([]Container, error) {
|
||||
var firewallRulesJSON sql.NullString
|
||||
if err := rows.Scan(
|
||||
&c.ID, &c.UUID, &c.Name, &c.Virtualization, &c.LXCName, &c.KVMName, &c.DiskImage, &c.MACAddress, &c.Template,
|
||||
&c.VCPU, &c.RAMMB, &c.DiskGB, &c.NetworkBWMbps, &c.MonthlyTrafficGB, &c.TrafficMode, &c.TrafficInGB,
|
||||
&c.TrafficOutGB, &c.TrafficUsedRX, &c.TrafficUsedTX, &c.TrafficResetDate, &c.IOSpeedMBps,
|
||||
&c.VCPU, &c.RAMMB, &c.DiskGB, &c.NetworkBWMbps, &c.NetworkDownMbps, &c.NetworkUpMbps,
|
||||
&c.MonthlyTrafficGB, &c.TrafficMode, &c.TrafficInGB,
|
||||
&c.TrafficOutGB, &c.TrafficUsedRX, &c.TrafficUsedTX, &c.TrafficResetDate,
|
||||
&c.IOSpeedMBps, &c.IOReadMBps, &c.IOWriteMBps,
|
||||
&c.Status, &c.IP, &c.IPv6, &c.IPv6PrefixLen, &c.IPv6Interface, &c.VNCPort, &c.SSHPort, &c.SSHPassword,
|
||||
&c.SSHHostKey, &c.PortMappingLimit, &c.SnapshotLimit, &c.CreatedAt, &c.ExpiresAt,
|
||||
&scheduleEnabled, &c.SnapshotScheduleIntervalHours, &c.SnapshotScheduleTime,
|
||||
@@ -828,6 +935,7 @@ func loadContainers() ([]Container, error) {
|
||||
if firewallRulesJSON.Valid && strings.TrimSpace(firewallRulesJSON.String) != "" {
|
||||
_ = json.Unmarshal([]byte(firewallRulesJSON.String), &c.FirewallRules)
|
||||
}
|
||||
NormalizeContainerResourceAliases(&c)
|
||||
result = append(result, c)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
@@ -1021,8 +1129,10 @@ func loadTasks() ([]SavedTask, error) {
|
||||
rows, err := db.Query(`SELECT
|
||||
id, type, container_id, container_name, status, error, created_at, template_id, user, ip, user_agent,
|
||||
cfg_name, cfg_virtualization, cfg_template_id, cfg_vcpu, cfg_cpu_percent, cfg_ram_mb, cfg_disk_gb,
|
||||
cfg_network_bw_mbps, cfg_monthly_traffic_gb, cfg_traffic_mode, cfg_traffic_in_gb,
|
||||
cfg_traffic_out_gb, cfg_io_speed_mbps, cfg_port_mapping_count, cfg_assign_nat, cfg_snapshot_limit,
|
||||
cfg_network_bw_mbps, cfg_network_down_mbps, cfg_network_up_mbps,
|
||||
cfg_monthly_traffic_gb, cfg_traffic_mode, cfg_traffic_in_gb,
|
||||
cfg_traffic_out_gb, cfg_io_speed_mbps, cfg_io_read_mbps, cfg_io_write_mbps,
|
||||
cfg_port_mapping_count, cfg_assign_nat, cfg_snapshot_limit,
|
||||
cfg_assign_ipv4, cfg_ipv4_count, cfg_public_ipv4s, cfg_assign_ipv6, cfg_ipv6_count, cfg_ipv6_addresses,
|
||||
cfg_ssh_auth_mode, cfg_ssh_password, cfg_ssh_public_key, cfg_expires_at
|
||||
FROM tasks ORDER BY created_at, id`)
|
||||
@@ -1042,8 +1152,10 @@ func loadTasks() ([]SavedTask, error) {
|
||||
if err := rows.Scan(
|
||||
&t.ID, &t.Type, &t.ContainerID, &t.ContainerName, &t.Status, &t.Error, &t.CreatedAt, &t.TemplateID, &t.User, &ip, &userAgent,
|
||||
&cfg.Name, &cfg.Virtualization, &cfg.TemplateID, &cfg.VCPU, &cfg.CPUPercent, &cfg.RAMMB, &cfg.DiskGB,
|
||||
&cfg.NetworkBWMbps, &cfg.MonthlyTrafficGB, &cfg.TrafficMode, &cfg.TrafficInGB,
|
||||
&cfg.TrafficOutGB, &cfg.IOSpeedMBps, &cfg.PortMappingCount, &assignNAT, &cfg.SnapshotLimit,
|
||||
&cfg.NetworkBWMbps, &cfg.NetworkDownMbps, &cfg.NetworkUpMbps,
|
||||
&cfg.MonthlyTrafficGB, &cfg.TrafficMode, &cfg.TrafficInGB,
|
||||
&cfg.TrafficOutGB, &cfg.IOSpeedMBps, &cfg.IOReadMBps, &cfg.IOWriteMBps,
|
||||
&cfg.PortMappingCount, &assignNAT, &cfg.SnapshotLimit,
|
||||
&assignIPv4, &ipv4Count, &publicIPv4s, &assignIPv6, &ipv6Count, &ipv6Addresses,
|
||||
&sshAuthMode, &sshPassword, &sshPublicKey, &cfg.ExpiresAt,
|
||||
); err != nil {
|
||||
@@ -1068,6 +1180,7 @@ func loadTasks() ([]SavedTask, error) {
|
||||
cfg.SSHAuthMode = sshAuthMode.String
|
||||
cfg.SSHPassword = sshPassword.String
|
||||
cfg.SSHPublicKey = sshPublicKey.String
|
||||
normalizeSavedTaskConfigLimits(&cfg)
|
||||
result = append(result, t)
|
||||
configs = append(configs, cfg)
|
||||
}
|
||||
|
||||
+59
-26
@@ -346,6 +346,7 @@ func normalizeQCOW2(ctx context.Context, src, target string) error {
|
||||
}
|
||||
|
||||
func (m *Manager) CreateContainer(cfg lxc.ContainerConfig) error {
|
||||
cfg.NormalizeResourceAliases()
|
||||
image := FindImage(cfg.TemplateID)
|
||||
if image == nil {
|
||||
return fmt.Errorf("KVM image not found: %s", cfg.TemplateID)
|
||||
@@ -456,7 +457,7 @@ func (m *Manager) defineContainer(id int, vmName string, cfg lxc.ContainerConfig
|
||||
if err := createWindowsUnattendISO(unattendPath, cfg.Name, winAdminPassword, ipv6List, ipv4List); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xml = windowsDomainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, ImagePath(image.ID), unattendPath, mac, cfg.IOSpeedMBps, cfg.NetworkBWMbps)
|
||||
xml = windowsDomainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, ImagePath(image.ID), unattendPath, mac, cfg.IOReadMBps, cfg.IOWriteMBps, cfg.NetworkDownMbps, cfg.NetworkUpMbps)
|
||||
} else {
|
||||
if image.Desktop != "" {
|
||||
if cfg.RAMMB < 2048 {
|
||||
@@ -472,7 +473,7 @@ func (m *Manager) defineContainer(id int, vmName string, cfg lxc.ContainerConfig
|
||||
if err := createSeedISO(seedPath, vmName, cfg.Name, sshPassword, sshPublicKey, mac, ipv6List, ipv4List, *image, sshAuthMode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xml = domainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, seedPath, mac, cfg.IOSpeedMBps, cfg.NetworkBWMbps, image.Desktop != "")
|
||||
xml = domainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, seedPath, mac, cfg.IOReadMBps, cfg.IOWriteMBps, cfg.NetworkDownMbps, cfg.NetworkUpMbps, image.Desktop != "")
|
||||
}
|
||||
xmlPath := filepath.Join(m.instanceDir(vmName), "domain.xml")
|
||||
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
||||
@@ -542,12 +543,16 @@ func (m *Manager) defineContainer(id int, vmName string, cfg lxc.ContainerConfig
|
||||
RAMMB: cfg.RAMMB,
|
||||
DiskGB: cfg.DiskGB,
|
||||
NetworkBWMbps: cfg.NetworkBWMbps,
|
||||
NetworkDownMbps: cfg.NetworkDownMbps,
|
||||
NetworkUpMbps: cfg.NetworkUpMbps,
|
||||
MonthlyTrafficGB: cfg.MonthlyTrafficGB,
|
||||
TrafficMode: trafficMode,
|
||||
TrafficInGB: cfg.TrafficInGB,
|
||||
TrafficOutGB: cfg.TrafficOutGB,
|
||||
TrafficResetDate: now[:7],
|
||||
IOSpeedMBps: cfg.IOSpeedMBps,
|
||||
IOReadMBps: cfg.IOReadMBps,
|
||||
IOWriteMBps: cfg.IOWriteMBps,
|
||||
PublicIPv4s: publicIPv4s,
|
||||
IPv6Addresses: ipv6Assignments,
|
||||
Status: "stopped",
|
||||
@@ -781,11 +786,15 @@ func (m *Manager) ReinstallContainer(id int, templateID string, authConfig ...lx
|
||||
RAMMB: c.RAMMB,
|
||||
DiskGB: c.DiskGB,
|
||||
NetworkBWMbps: c.NetworkBWMbps,
|
||||
NetworkDownMbps: c.NetworkDownMbps,
|
||||
NetworkUpMbps: c.NetworkUpMbps,
|
||||
MonthlyTrafficGB: c.MonthlyTrafficGB,
|
||||
TrafficMode: c.TrafficMode,
|
||||
TrafficInGB: c.TrafficInGB,
|
||||
TrafficOutGB: c.TrafficOutGB,
|
||||
IOSpeedMBps: c.IOSpeedMBps,
|
||||
IOReadMBps: c.IOReadMBps,
|
||||
IOWriteMBps: c.IOWriteMBps,
|
||||
PortMappingCount: c.PortMappingLimit,
|
||||
SnapshotLimit: c.SnapshotLimit,
|
||||
ExpiresAt: c.ExpiresAt,
|
||||
@@ -882,6 +891,7 @@ func (m *Manager) ApplyContainerLimits(c *config.Container) error {
|
||||
if c == nil || !c.IsKVM() {
|
||||
return nil
|
||||
}
|
||||
config.NormalizeContainerResourceAliases(c)
|
||||
if c.Status == "running" {
|
||||
// Config already saved; domain definition will be refreshed on next start
|
||||
return nil
|
||||
@@ -893,10 +903,10 @@ func (m *Manager) ApplyContainerLimits(c *config.Container) error {
|
||||
if IsWindowsImage(c.Template) {
|
||||
winISO := ImagePath(c.Template)
|
||||
unattendISO := existingWindowsUnattendISO(m.instanceDir(c.VirshName()))
|
||||
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
||||
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOReadMBps, c.IOWriteMBps, c.NetworkDownMbps, c.NetworkUpMbps)
|
||||
} else {
|
||||
seedPath := filepath.Join(m.instanceDir(c.VirshName()), "seed.iso")
|
||||
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps, isKVMDesktopTemplate(c.Template))
|
||||
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOReadMBps, c.IOWriteMBps, c.NetworkDownMbps, c.NetworkUpMbps, isKVMDesktopTemplate(c.Template))
|
||||
}
|
||||
xmlPath := filepath.Join(m.instanceDir(c.VirshName()), "domain.xml")
|
||||
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
||||
@@ -913,15 +923,16 @@ func (m *Manager) ensureDomainDefinition(c *config.Container) error {
|
||||
if c == nil || !c.IsKVM() || c.DiskImage == "" || c.MACAddress == "" {
|
||||
return nil
|
||||
}
|
||||
config.NormalizeContainerResourceAliases(c)
|
||||
var xml string
|
||||
xmlPath := filepath.Join(m.instanceDir(c.VirshName()), "domain.xml")
|
||||
if IsWindowsImage(c.Template) {
|
||||
winISO := ImagePath(c.Template)
|
||||
unattendISO := existingWindowsUnattendISO(m.instanceDir(c.VirshName()))
|
||||
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
||||
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOReadMBps, c.IOWriteMBps, c.NetworkDownMbps, c.NetworkUpMbps)
|
||||
} else {
|
||||
seedPath := filepath.Join(m.instanceDir(c.VirshName()), "seed.iso")
|
||||
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps, isKVMDesktopTemplate(c.Template))
|
||||
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOReadMBps, c.IOWriteMBps, c.NetworkDownMbps, c.NetworkUpMbps, isKVMDesktopTemplate(c.Template))
|
||||
}
|
||||
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
||||
return err
|
||||
@@ -2079,7 +2090,7 @@ func isKVMDesktopTemplate(templateID string) bool {
|
||||
return image != nil && image.Desktop != ""
|
||||
}
|
||||
|
||||
func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string, ioSpeedMBps int, networkBWMbps int, desktop bool) string {
|
||||
func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string, ioReadMBps int, ioWriteMBps int, networkDownMbps int, networkUpMbps int, desktop bool) string {
|
||||
if vcpu < 1 {
|
||||
vcpu = 1
|
||||
}
|
||||
@@ -2087,21 +2098,32 @@ func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string,
|
||||
ramMB = 512
|
||||
}
|
||||
iotune := ""
|
||||
if ioSpeedMBps > 0 {
|
||||
bytesPerSecond := int64(ioSpeedMBps) * 1024 * 1024
|
||||
if ioReadMBps > 0 || ioWriteMBps > 0 {
|
||||
var parts []string
|
||||
if ioReadMBps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <read_bytes_sec>%d</read_bytes_sec>", int64(ioReadMBps)*1024*1024))
|
||||
}
|
||||
if ioWriteMBps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <write_bytes_sec>%d</write_bytes_sec>", int64(ioWriteMBps)*1024*1024))
|
||||
}
|
||||
iotune = fmt.Sprintf(`
|
||||
<iotune>
|
||||
<total_bytes_sec>%d</total_bytes_sec>
|
||||
</iotune>`, bytesPerSecond)
|
||||
%s
|
||||
</iotune>`, strings.Join(parts, "\n"))
|
||||
}
|
||||
bandwidth := ""
|
||||
if networkBWMbps > 0 {
|
||||
averageKiB := networkBWMbps * 128
|
||||
if networkDownMbps > 0 || networkUpMbps > 0 {
|
||||
var parts []string
|
||||
if networkDownMbps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <inbound average='%d'/>", networkDownMbps*128))
|
||||
}
|
||||
if networkUpMbps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <outbound average='%d'/>", networkUpMbps*128))
|
||||
}
|
||||
bandwidth = fmt.Sprintf(`
|
||||
<bandwidth>
|
||||
<inbound average='%d'/>
|
||||
<outbound average='%d'/>
|
||||
</bandwidth>`, averageKiB, averageKiB)
|
||||
%s
|
||||
</bandwidth>`, strings.Join(parts, "\n"))
|
||||
}
|
||||
video := "<video><model type='virtio'/></video>"
|
||||
input := ""
|
||||
@@ -2158,7 +2180,7 @@ func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string,
|
||||
</domain>`, xmlEscape(name), domainUUIDXML(name), ramMB, ramMB, vcpu, vcpu, xmlEscape(diskPath), iotune, xmlEscape(seedPath), xmlEscape(mac), bandwidth, input, video)
|
||||
}
|
||||
|
||||
func windowsDomainXML(name string, vcpu int, ramMB int, diskPath, winISOPath, unattendISOPath, mac string, ioSpeedMBps int, networkBWMbps int) string {
|
||||
func windowsDomainXML(name string, vcpu int, ramMB int, diskPath, winISOPath, unattendISOPath, mac string, ioReadMBps int, ioWriteMBps int, networkDownMbps int, networkUpMbps int) string {
|
||||
if vcpu < 1 {
|
||||
vcpu = 1
|
||||
}
|
||||
@@ -2166,21 +2188,32 @@ func windowsDomainXML(name string, vcpu int, ramMB int, diskPath, winISOPath, un
|
||||
ramMB = 2048
|
||||
}
|
||||
iotune := ""
|
||||
if ioSpeedMBps > 0 {
|
||||
bytesPerSecond := int64(ioSpeedMBps) * 1024 * 1024
|
||||
if ioReadMBps > 0 || ioWriteMBps > 0 {
|
||||
var parts []string
|
||||
if ioReadMBps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <read_bytes_sec>%d</read_bytes_sec>", int64(ioReadMBps)*1024*1024))
|
||||
}
|
||||
if ioWriteMBps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <write_bytes_sec>%d</write_bytes_sec>", int64(ioWriteMBps)*1024*1024))
|
||||
}
|
||||
iotune = fmt.Sprintf(`
|
||||
<iotune>
|
||||
<total_bytes_sec>%d</total_bytes_sec>
|
||||
</iotune>`, bytesPerSecond)
|
||||
%s
|
||||
</iotune>`, strings.Join(parts, "\n"))
|
||||
}
|
||||
bandwidth := ""
|
||||
if networkBWMbps > 0 {
|
||||
averageKiB := networkBWMbps * 128
|
||||
if networkDownMbps > 0 || networkUpMbps > 0 {
|
||||
var parts []string
|
||||
if networkDownMbps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <inbound average='%d'/>", networkDownMbps*128))
|
||||
}
|
||||
if networkUpMbps > 0 {
|
||||
parts = append(parts, fmt.Sprintf(" <outbound average='%d'/>", networkUpMbps*128))
|
||||
}
|
||||
bandwidth = fmt.Sprintf(`
|
||||
<bandwidth>
|
||||
<inbound average='%d'/>
|
||||
<outbound average='%d'/>
|
||||
</bandwidth>`, averageKiB, averageKiB)
|
||||
%s
|
||||
</bandwidth>`, strings.Join(parts, "\n"))
|
||||
}
|
||||
virtioWinISO := virtioWinISOPath()
|
||||
unattendDisk := ""
|
||||
|
||||
+103
-34
@@ -226,11 +226,15 @@ type ContainerConfig struct {
|
||||
RAMMB int `json:"ram_mb"`
|
||||
DiskGB int `json:"disk_gb"`
|
||||
NetworkBWMbps int `json:"network_bw_mbps"`
|
||||
NetworkDownMbps int `json:"network_down_mbps"`
|
||||
NetworkUpMbps int `json:"network_up_mbps"`
|
||||
MonthlyTrafficGB int `json:"monthly_traffic_gb"`
|
||||
TrafficMode string `json:"traffic_mode"` // "total" or "in_out"
|
||||
TrafficInGB int `json:"traffic_in_gb"` // 0=unlimited
|
||||
TrafficOutGB int `json:"traffic_out_gb"` // 0=unlimited
|
||||
IOSpeedMBps int `json:"io_speed_mbps"`
|
||||
IOReadMBps int `json:"io_read_mbps"`
|
||||
IOWriteMBps int `json:"io_write_mbps"`
|
||||
ExtraPorts []int `json:"extra_ports"`
|
||||
PortMappingCount int `json:"port_mapping_count"`
|
||||
AssignNAT *bool `json:"assign_nat,omitempty"`
|
||||
@@ -247,12 +251,48 @@ type ContainerConfig struct {
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
}
|
||||
|
||||
func (cfg *ContainerConfig) NormalizeResourceAliases() {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
if cfg.NetworkBWMbps < 0 {
|
||||
cfg.NetworkBWMbps = 0
|
||||
}
|
||||
if cfg.NetworkDownMbps < 0 {
|
||||
cfg.NetworkDownMbps = 0
|
||||
}
|
||||
if cfg.NetworkUpMbps < 0 {
|
||||
cfg.NetworkUpMbps = 0
|
||||
}
|
||||
if cfg.NetworkDownMbps == 0 && cfg.NetworkUpMbps == 0 && cfg.NetworkBWMbps > 0 {
|
||||
cfg.NetworkDownMbps = cfg.NetworkBWMbps
|
||||
cfg.NetworkUpMbps = cfg.NetworkBWMbps
|
||||
}
|
||||
cfg.NetworkBWMbps = config.LegacySymmetricLimit(cfg.NetworkDownMbps, cfg.NetworkUpMbps)
|
||||
|
||||
if cfg.IOSpeedMBps < 0 {
|
||||
cfg.IOSpeedMBps = 0
|
||||
}
|
||||
if cfg.IOReadMBps < 0 {
|
||||
cfg.IOReadMBps = 0
|
||||
}
|
||||
if cfg.IOWriteMBps < 0 {
|
||||
cfg.IOWriteMBps = 0
|
||||
}
|
||||
if cfg.IOReadMBps == 0 && cfg.IOWriteMBps == 0 && cfg.IOSpeedMBps > 0 {
|
||||
cfg.IOReadMBps = cfg.IOSpeedMBps
|
||||
cfg.IOWriteMBps = cfg.IOSpeedMBps
|
||||
}
|
||||
cfg.IOSpeedMBps = config.LegacySymmetricLimit(cfg.IOReadMBps, cfg.IOWriteMBps)
|
||||
}
|
||||
|
||||
func (cfg ContainerConfig) WantsNAT() bool {
|
||||
return cfg.AssignNAT == nil || *cfg.AssignNAT
|
||||
}
|
||||
|
||||
// CreateContainer creates a new LXC container. Uses ct-{id} as LXC name internally.
|
||||
func (m *Manager) CreateContainer(cfg ContainerConfig) error {
|
||||
cfg.NormalizeResourceAliases()
|
||||
tmpl := FindTemplate(cfg.TemplateID)
|
||||
if tmpl == nil {
|
||||
return fmt.Errorf("template not found: %s", cfg.TemplateID)
|
||||
@@ -389,12 +429,16 @@ func (m *Manager) CreateContainer(cfg ContainerConfig) error {
|
||||
RAMMB: cfg.RAMMB,
|
||||
DiskGB: cfg.DiskGB,
|
||||
NetworkBWMbps: cfg.NetworkBWMbps,
|
||||
NetworkDownMbps: cfg.NetworkDownMbps,
|
||||
NetworkUpMbps: cfg.NetworkUpMbps,
|
||||
MonthlyTrafficGB: cfg.MonthlyTrafficGB,
|
||||
TrafficMode: trafficMode,
|
||||
TrafficInGB: cfg.TrafficInGB,
|
||||
TrafficOutGB: cfg.TrafficOutGB,
|
||||
TrafficResetDate: trafficResetDate,
|
||||
IOSpeedMBps: cfg.IOSpeedMBps,
|
||||
IOReadMBps: cfg.IOReadMBps,
|
||||
IOWriteMBps: cfg.IOWriteMBps,
|
||||
Status: "stopped",
|
||||
IP: "",
|
||||
PublicIPv4s: publicIPv4s,
|
||||
@@ -531,6 +575,7 @@ func (m *Manager) preconfigureSSH(rootfsPath, templateID string, sshAuthMode str
|
||||
|
||||
// applyResourceLimits applies cgroup v2 limits and mandatory security hardening to container config.
|
||||
func (m *Manager) applyResourceLimits(lxcName string, cfg ContainerConfig) error {
|
||||
cfg.NormalizeResourceAliases()
|
||||
configFile := filepath.Join(m.LxcPath, lxcName, "config")
|
||||
|
||||
data, err := os.ReadFile(configFile)
|
||||
@@ -599,12 +644,12 @@ func (m *Manager) applyResourceLimits(lxcName string, cfg ContainerConfig) error
|
||||
ramBytes := int64(cfg.RAMMB) * 1024 * 1024
|
||||
newLines = append(newLines, fmt.Sprintf("lxc.cgroup2.memory.max = %d", ramBytes))
|
||||
}
|
||||
if cfg.IOSpeedMBps > 0 {
|
||||
if cfg.IOReadMBps > 0 || cfg.IOWriteMBps > 0 {
|
||||
// Note: lxc.cgroup2.io.max is skipped for unprivileged containers because
|
||||
// LXC's cgfsng_setup_limits cannot resolve host device numbers (e.g. 8:1)
|
||||
// in the unprivileged namespace context.
|
||||
// IO limits are instead applied post-start via direct cgroup2 writes.
|
||||
fmt.Printf("Info: IO limit (%d MB/s) for %s will be applied post-start via cgroup2\n", cfg.IOSpeedMBps, lxcName)
|
||||
fmt.Printf("Info: IO limit (read=%d MB/s write=%d MB/s) for %s will be applied post-start via cgroup2\n", cfg.IOReadMBps, cfg.IOWriteMBps, lxcName)
|
||||
}
|
||||
|
||||
newContent := strings.Join(newLines, "\n")
|
||||
@@ -614,18 +659,28 @@ func (m *Manager) applyResourceLimits(lxcName string, cfg ContainerConfig) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) ioLimitLines(lxcName string, mbps int) ([]string, error) {
|
||||
if mbps <= 0 {
|
||||
return nil, nil
|
||||
func (m *Manager) ioLimitLines(lxcName string, readMBps int, writeMBps int) ([]string, error) {
|
||||
if readMBps < 0 {
|
||||
readMBps = 0
|
||||
}
|
||||
if writeMBps < 0 {
|
||||
writeMBps = 0
|
||||
}
|
||||
devices, err := m.rootfsBlockDevices(lxcName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ioBytes := mbps * 1024 * 1024
|
||||
readValue := "max"
|
||||
if readMBps > 0 {
|
||||
readValue = strconv.Itoa(readMBps * 1024 * 1024)
|
||||
}
|
||||
writeValue := "max"
|
||||
if writeMBps > 0 {
|
||||
writeValue = strconv.Itoa(writeMBps * 1024 * 1024)
|
||||
}
|
||||
lines := make([]string, 0, len(devices))
|
||||
for _, device := range devices {
|
||||
lines = append(lines, fmt.Sprintf("%s rbps=%d wbps=%d", device, ioBytes, ioBytes))
|
||||
lines = append(lines, fmt.Sprintf("%s rbps=%s wbps=%s", device, readValue, writeValue))
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
@@ -1253,8 +1308,12 @@ func (m *Manager) StartContainer(id int) error {
|
||||
RAMMB: c.RAMMB,
|
||||
DiskGB: c.DiskGB,
|
||||
NetworkBWMbps: c.NetworkBWMbps,
|
||||
NetworkDownMbps: c.NetworkDownMbps,
|
||||
NetworkUpMbps: c.NetworkUpMbps,
|
||||
MonthlyTrafficGB: c.MonthlyTrafficGB,
|
||||
IOSpeedMBps: c.IOSpeedMBps,
|
||||
IOReadMBps: c.IOReadMBps,
|
||||
IOWriteMBps: c.IOWriteMBps,
|
||||
AssignIPv6: c.IPv6 != "" || len(c.IPv6Addresses) > 0,
|
||||
ExpiresAt: c.ExpiresAt,
|
||||
}); err != nil {
|
||||
@@ -1378,6 +1437,7 @@ func (m *Manager) ApplyContainerLimits(c *config.Container) error {
|
||||
if c == nil || c.Status != "running" {
|
||||
return nil
|
||||
}
|
||||
config.NormalizeContainerResourceAliases(c)
|
||||
lxcName := c.LxcName()
|
||||
|
||||
// CPU: write cpu.max
|
||||
@@ -1400,48 +1460,52 @@ func (m *Manager) ApplyContainerLimits(c *config.Container) error {
|
||||
os.WriteFile(path, []byte(memLine), 0644)
|
||||
}
|
||||
|
||||
// IO speed: write io.max
|
||||
if c.IOSpeedMBps > 0 {
|
||||
ioLines, err := m.ioLimitLines(lxcName, c.IOSpeedMBps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioLine := strings.Join(ioLines, "\n")
|
||||
for _, path := range []string{
|
||||
fmt.Sprintf("/sys/fs/cgroup/lxc/%s/io.max", lxcName),
|
||||
fmt.Sprintf("/sys/fs/cgroup/lxc.payload.%s/io.max", lxcName),
|
||||
} {
|
||||
os.WriteFile(path, []byte(ioLine), 0644)
|
||||
}
|
||||
// IO speed: write io.max, including max values to clear old per-direction limits.
|
||||
ioLines, err := m.ioLimitLines(lxcName, c.IOReadMBps, c.IOWriteMBps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioLine := strings.Join(ioLines, "\n")
|
||||
for _, path := range []string{
|
||||
fmt.Sprintf("/sys/fs/cgroup/lxc/%s/io.max", lxcName),
|
||||
fmt.Sprintf("/sys/fs/cgroup/lxc.payload.%s/io.max", lxcName),
|
||||
} {
|
||||
os.WriteFile(path, []byte(ioLine), 0644)
|
||||
}
|
||||
|
||||
// Network bandwidth
|
||||
if c.NetworkBWMbps > 0 {
|
||||
m.applyBandwidthLimit(lxcName, c.NetworkBWMbps)
|
||||
} else {
|
||||
m.cleanupBandwidthLimit(lxcName)
|
||||
}
|
||||
m.applyBandwidthLimit(lxcName, c.NetworkDownMbps, c.NetworkUpMbps)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) applyBandwidthLimit(lxcName string, mbps int) {
|
||||
func (m *Manager) applyBandwidthLimit(lxcName string, downMbps int, upMbps int) {
|
||||
veth := m.getContainerVethByNS(lxcName)
|
||||
if veth == "" {
|
||||
fmt.Printf("Warning: could not find veth for %s\n", lxcName)
|
||||
return
|
||||
}
|
||||
rate := fmt.Sprintf("%dmbit", mbps)
|
||||
burst := fmt.Sprintf("%dkbit", mbps*100)
|
||||
exec.Command("tc", "qdisc", "del", "dev", veth, "root").Run()
|
||||
exec.Command("tc", "qdisc", "add", "dev", veth, "root", "handle", "1:", "htb", "default", "10").Run()
|
||||
exec.Command("tc", "class", "add", "dev", veth, "parent", "1:", "classid", "1:10", "htb", "rate", rate, "burst", burst).Run()
|
||||
fmt.Printf("Bandwidth limit: %s = %d Mbps on %s\n", lxcName, mbps, veth)
|
||||
exec.Command("tc", "qdisc", "del", "dev", veth, "ingress").Run()
|
||||
if downMbps > 0 {
|
||||
rate := fmt.Sprintf("%dmbit", downMbps)
|
||||
burst := fmt.Sprintf("%dkbit", downMbps*100)
|
||||
exec.Command("tc", "qdisc", "add", "dev", veth, "root", "handle", "1:", "htb", "default", "10").Run()
|
||||
exec.Command("tc", "class", "add", "dev", veth, "parent", "1:", "classid", "1:10", "htb", "rate", rate, "burst", burst).Run()
|
||||
}
|
||||
if upMbps > 0 {
|
||||
rate := fmt.Sprintf("%dmbit", upMbps)
|
||||
burst := fmt.Sprintf("%dkbit", upMbps*100)
|
||||
exec.Command("tc", "qdisc", "add", "dev", veth, "handle", "ffff:", "ingress").Run()
|
||||
exec.Command("tc", "filter", "add", "dev", veth, "parent", "ffff:", "protocol", "all", "u32", "match", "u32", "0", "0", "police", "rate", rate, "burst", burst, "drop", "flowid", ":1").Run()
|
||||
}
|
||||
fmt.Printf("Bandwidth limit: %s down=%d Mbps up=%d Mbps on %s\n", lxcName, downMbps, upMbps, veth)
|
||||
}
|
||||
|
||||
func (m *Manager) cleanupBandwidthLimit(lxcName string) {
|
||||
veth := m.getContainerVethByNS(lxcName)
|
||||
if veth != "" {
|
||||
exec.Command("tc", "qdisc", "del", "dev", veth, "root").Run()
|
||||
exec.Command("tc", "qdisc", "del", "dev", veth, "ingress").Run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2447,6 +2511,8 @@ func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) {
|
||||
RAMMB: 512,
|
||||
DiskGB: 10,
|
||||
NetworkBWMbps: 100,
|
||||
NetworkDownMbps: 100,
|
||||
NetworkUpMbps: 100,
|
||||
MonthlyTrafficGB: 1000,
|
||||
TrafficMode: "total",
|
||||
Status: status,
|
||||
@@ -2610,8 +2676,12 @@ func (m *Manager) ReinstallContainer(id int, templateID string, authConfig ...Co
|
||||
RAMMB: c.RAMMB,
|
||||
DiskGB: c.DiskGB,
|
||||
NetworkBWMbps: c.NetworkBWMbps,
|
||||
NetworkDownMbps: c.NetworkDownMbps,
|
||||
NetworkUpMbps: c.NetworkUpMbps,
|
||||
MonthlyTrafficGB: c.MonthlyTrafficGB,
|
||||
IOSpeedMBps: c.IOSpeedMBps,
|
||||
IOReadMBps: c.IOReadMBps,
|
||||
IOWriteMBps: c.IOWriteMBps,
|
||||
AssignIPv6: c.IPv6 != "" || len(c.IPv6Addresses) > 0,
|
||||
ExpiresAt: c.ExpiresAt,
|
||||
}
|
||||
@@ -2694,9 +2764,8 @@ func (m *Manager) ReinstallContainer(id int, templateID string, authConfig ...Co
|
||||
}
|
||||
}
|
||||
// Apply bandwidth limit after reinstall
|
||||
if c.NetworkBWMbps > 0 {
|
||||
m.applyBandwidthLimit(c.LxcName(), c.NetworkBWMbps)
|
||||
}
|
||||
config.NormalizeContainerResourceAliases(c)
|
||||
m.applyBandwidthLimit(c.LxcName(), c.NetworkDownMbps, c.NetworkUpMbps)
|
||||
if c.IPv6 != "" || len(c.IPv6Addresses) > 0 {
|
||||
if err := m.ApplyIPv6(id); err != nil {
|
||||
fmt.Printf("Warning: failed to apply IPv6 after reinstall: %v\n", err)
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user