225 lines
8.0 KiB
Go
225 lines
8.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"clawreef/internal/models"
|
|
|
|
"github.com/upper/db/v4"
|
|
)
|
|
|
|
type RuntimePodMetricsUpdate struct {
|
|
CPUMillisUsed int64
|
|
MemoryBytesUsed int64
|
|
DiskBytesUsed int64
|
|
NetworkRXBytes int64
|
|
NetworkTXBytes int64
|
|
MetricsJSON *string
|
|
LastSeenAt *time.Time
|
|
}
|
|
|
|
type RuntimePodRepository interface {
|
|
UpsertFromAgent(ctx context.Context, pod *models.RuntimePod) error
|
|
GetByID(ctx context.Context, id int64) (*models.RuntimePod, error)
|
|
GetByNamespaceName(ctx context.Context, namespace, podName string) (*models.RuntimePod, error)
|
|
List(ctx context.Context, runtimeType string) ([]models.RuntimePod, error)
|
|
ListSchedulable(ctx context.Context, runtimeType string) ([]models.RuntimePod, error)
|
|
TryClaimSlot(ctx context.Context, podID int64) (bool, error)
|
|
ReleaseSlot(ctx context.Context, podID int64) error
|
|
MarkState(ctx context.Context, podID int64, state string, draining bool) error
|
|
UpdateHeartbeat(ctx context.Context, podID int64, state string, usedSlots int, capacity int, draining bool, lastSeenAt time.Time) error
|
|
MarkUnseenUnhealthy(ctx context.Context, cutoff time.Time) error
|
|
UpdateMetrics(ctx context.Context, podID int64, metrics RuntimePodMetricsUpdate) error
|
|
}
|
|
|
|
type runtimePodRepository struct {
|
|
sess db.Session
|
|
}
|
|
|
|
func NewRuntimePodRepository(sess db.Session) RuntimePodRepository {
|
|
return &runtimePodRepository{sess: sess}
|
|
}
|
|
|
|
func (r *runtimePodRepository) UpsertFromAgent(ctx context.Context, pod *models.RuntimePod) error {
|
|
ensureTimestamps(&pod.CreatedAt, &pod.UpdatedAt)
|
|
res, err := r.sess.SQL().ExecContext(ctx, `
|
|
INSERT INTO runtime_pods (
|
|
runtime_type, namespace, pod_name, pod_uid, pod_ip, node_name, deployment_name,
|
|
image_ref, agent_endpoint, state, capacity, used_slots, draining, cpu_millis_used,
|
|
memory_bytes_used, disk_bytes_used, network_rx_bytes, network_tx_bytes, metrics_json,
|
|
last_seen_at, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
id = LAST_INSERT_ID(id),
|
|
runtime_type = VALUES(runtime_type),
|
|
pod_uid = VALUES(pod_uid),
|
|
pod_ip = VALUES(pod_ip),
|
|
node_name = VALUES(node_name),
|
|
deployment_name = VALUES(deployment_name),
|
|
image_ref = VALUES(image_ref),
|
|
agent_endpoint = VALUES(agent_endpoint),
|
|
capacity = VALUES(capacity),
|
|
cpu_millis_used = VALUES(cpu_millis_used),
|
|
memory_bytes_used = VALUES(memory_bytes_used),
|
|
disk_bytes_used = VALUES(disk_bytes_used),
|
|
network_rx_bytes = VALUES(network_rx_bytes),
|
|
network_tx_bytes = VALUES(network_tx_bytes),
|
|
metrics_json = VALUES(metrics_json),
|
|
last_seen_at = VALUES(last_seen_at),
|
|
updated_at = VALUES(updated_at)
|
|
`, pod.RuntimeType, pod.Namespace, pod.PodName, pod.PodUID, pod.PodIP, pod.NodeName, pod.DeploymentName,
|
|
pod.ImageRef, pod.AgentEndpoint, pod.State, pod.Capacity, pod.UsedSlots, pod.Draining, pod.CPUMillisUsed,
|
|
pod.MemoryBytesUsed, pod.DiskBytesUsed, pod.NetworkRXBytes, pod.NetworkTXBytes, pod.MetricsJSON,
|
|
pod.LastSeenAt, pod.CreatedAt, pod.UpdatedAt)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to upsert runtime pod: %w", err)
|
|
}
|
|
if id, err := res.LastInsertId(); err == nil {
|
|
pod.ID = id
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) GetByID(ctx context.Context, id int64) (*models.RuntimePod, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
var pod models.RuntimePod
|
|
if err := r.sess.Collection("runtime_pods").Find(db.Cond{"id": id}).One(&pod); err != nil {
|
|
if err == db.ErrNoMoreRows {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to get runtime pod: %w", err)
|
|
}
|
|
return &pod, nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) GetByNamespaceName(ctx context.Context, namespace, podName string) (*models.RuntimePod, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
var pod models.RuntimePod
|
|
if err := r.sess.Collection("runtime_pods").Find(db.Cond{"namespace": namespace, "pod_name": podName}).One(&pod); err != nil {
|
|
if err == db.ErrNoMoreRows {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to get runtime pod by namespace/name: %w", err)
|
|
}
|
|
return &pod, nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) List(ctx context.Context, runtimeType string) ([]models.RuntimePod, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
cond := db.Cond{}
|
|
if runtimeType != "" {
|
|
cond["runtime_type"] = runtimeType
|
|
}
|
|
var pods []models.RuntimePod
|
|
if err := r.sess.Collection("runtime_pods").Find(cond).OrderBy("namespace", "pod_name").All(&pods); err != nil {
|
|
return nil, fmt.Errorf("failed to list runtime pods: %w", err)
|
|
}
|
|
return pods, nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) ListSchedulable(ctx context.Context, runtimeType string) ([]models.RuntimePod, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
var pods []models.RuntimePod
|
|
iter := r.sess.SQL().IteratorContext(ctx, `
|
|
SELECT *
|
|
FROM runtime_pods
|
|
WHERE runtime_type = ? AND state = 'ready' AND draining = 0 AND used_slots < capacity
|
|
ORDER BY used_slots, id
|
|
`, runtimeType)
|
|
defer iter.Close()
|
|
if err := iter.All(&pods); err != nil {
|
|
return nil, fmt.Errorf("failed to list schedulable runtime pods: %w", err)
|
|
}
|
|
return pods, nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) TryClaimSlot(ctx context.Context, podID int64) (bool, error) {
|
|
res, err := r.sess.SQL().ExecContext(ctx, `
|
|
UPDATE runtime_pods
|
|
SET used_slots = used_slots + 1, updated_at = ?
|
|
WHERE id = ? AND state = 'ready' AND draining = 0 AND used_slots < capacity
|
|
`, time.Now().UTC(), podID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to claim runtime pod slot: %w", err)
|
|
}
|
|
affected, err := res.RowsAffected()
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to inspect runtime pod slot claim: %w", err)
|
|
}
|
|
return affected == 1, nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) ReleaseSlot(ctx context.Context, podID int64) error {
|
|
_, err := r.sess.SQL().ExecContext(ctx, `
|
|
UPDATE runtime_pods
|
|
SET used_slots = CASE WHEN used_slots > 0 THEN used_slots - 1 ELSE 0 END, updated_at = ?
|
|
WHERE id = ?
|
|
`, time.Now().UTC(), podID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to release runtime pod slot: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) MarkState(ctx context.Context, podID int64, state string, draining bool) error {
|
|
_, err := r.sess.SQL().ExecContext(ctx, `
|
|
UPDATE runtime_pods
|
|
SET state = ?, draining = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`, state, draining, time.Now().UTC(), podID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to mark runtime pod state: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) UpdateHeartbeat(ctx context.Context, podID int64, state string, usedSlots int, capacity int, draining bool, lastSeenAt time.Time) error {
|
|
_, err := r.sess.SQL().ExecContext(ctx, `
|
|
UPDATE runtime_pods
|
|
SET state = ?, used_slots = ?, capacity = ?, draining = ?, last_seen_at = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`, state, usedSlots, capacity, draining, lastSeenAt, time.Now().UTC(), podID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update runtime pod heartbeat: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) MarkUnseenUnhealthy(ctx context.Context, cutoff time.Time) error {
|
|
_, err := r.sess.SQL().ExecContext(ctx, `
|
|
UPDATE runtime_pods
|
|
SET state = 'unhealthy', updated_at = ?
|
|
WHERE (last_seen_at IS NULL OR last_seen_at < ?) AND state <> 'unhealthy'
|
|
`, time.Now().UTC(), cutoff)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to mark unseen runtime pods unhealthy: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *runtimePodRepository) UpdateMetrics(ctx context.Context, podID int64, metrics RuntimePodMetricsUpdate) error {
|
|
_, err := r.sess.SQL().ExecContext(ctx, `
|
|
UPDATE runtime_pods
|
|
SET cpu_millis_used = ?, memory_bytes_used = ?, disk_bytes_used = ?,
|
|
network_rx_bytes = ?, network_tx_bytes = ?, metrics_json = ?,
|
|
last_seen_at = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`, metrics.CPUMillisUsed, metrics.MemoryBytesUsed, metrics.DiskBytesUsed,
|
|
metrics.NetworkRXBytes, metrics.NetworkTXBytes, metrics.MetricsJSON,
|
|
metrics.LastSeenAt, time.Now().UTC(), podID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update runtime pod metrics: %w", err)
|
|
}
|
|
return nil
|
|
}
|