"csrc/vscode:/vscode.git/clone" did not exist on "f9a4087182ffcd9404779fcda876f820b3b26d5f"
metadata.go 7.43 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// metadata.go handles checkpoint metadata for cross-node restore operations.
package common

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"
)

const (
	// MetadataFilename is the name of the metadata file in checkpoint directories
	MetadataFilename = "metadata.json"
	// DescriptorsFilename is the name of the file descriptors file
	DescriptorsFilename = "descriptors.json"
)

// CheckpointMetadata stores information needed for cross-node restore
type CheckpointMetadata struct {
	// Checkpoint identification
	CheckpointID string    `json:"checkpoint_id"`
	CreatedAt    time.Time `json:"created_at"`

	// Source information
	SourceNode   string `json:"source_node"`
	SourcePodIP  string `json:"source_pod_ip,omitempty"` // For cross-node TCP detection
	ContainerID  string `json:"container_id"`
	PodName      string `json:"pod_name"`
	PodNamespace string `json:"pod_namespace"`
	Image        string `json:"image"`

	// Process information
	PID int `json:"pid"`

	// Filesystem information
	RootfsDiffPath  string `json:"rootfs_diff_path,omitempty"`   // Path to rootfs-diff.tar
	UpperDir        string `json:"upper_dir,omitempty"`          // Original overlay upperdir
	HasRootfsDiff   bool   `json:"has_rootfs_diff"`              // Whether rootfs diff was captured
	HasDeletedFiles bool   `json:"has_deleted_files"`            // Whether deleted files were tracked

	// Mount mappings from original container
	Mounts []MountMetadata `json:"mounts"`

	// OCI spec derived paths (populated from containerd, used at restore)
	// These replace hardcoded values with runtime-discovered configuration
	MaskedPaths    []string `json:"masked_paths,omitempty"`     // From OCI spec Linux.MaskedPaths
	ReadonlyPaths  []string `json:"readonly_paths,omitempty"`   // From OCI spec Linux.ReadonlyPaths
	BindMountDests []string `json:"bind_mount_dests,omitempty"` // Destinations of bind mounts (for tar exclusions)

	// Namespace information
	Namespaces []NamespaceMetadata `json:"namespaces"`

	// CRIU options used during checkpoint (for restore compatibility)
	CRIUOptions CRIUOptionsMetadata `json:"criu_options"`
}

// CRIUOptionsMetadata stores CRIU options used during checkpoint.
// This allows restore to use compatible options.
// Note: In our implementation, most options are hardcoded as always-on for K8s,
// but we store them for compatibility and debugging purposes.
type CRIUOptionsMetadata struct {
	TcpEstablished bool `json:"tcp_established"`
	TcpClose       bool `json:"tcp_close"`
	ShellJob       bool `json:"shell_job"`
	FileLocks      bool `json:"file_locks"`
	LeaveRunning   bool `json:"leave_running"`
	LinkRemap      bool `json:"link_remap"`
	ExtMasters     bool `json:"ext_masters"`
}

// MountMetadata stores information about a mount for remapping during restore
type MountMetadata struct {
	ContainerPath string   `json:"container_path"`           // Path inside container (e.g., /usr/share/nginx/html)
	HostPath      string   `json:"host_path"`                // Original host path from mountinfo
	OCISource     string   `json:"oci_source,omitempty"`     // Source path from OCI spec (may differ from HostPath)
	OCIType       string   `json:"oci_type,omitempty"`       // Mount type from OCI spec (bind, tmpfs, etc.)
	OCIOptions    []string `json:"oci_options,omitempty"`    // Mount options from OCI spec
	VolumeType    string   `json:"volume_type"`              // emptyDir, pvc, configMap, secret, hostPath (best-effort)
	VolumeName    string   `json:"volume_name"`              // Kubernetes volume name (best-effort from path parsing)
	FSType        string   `json:"fs_type"`                  // Filesystem type from mountinfo
	ReadOnly      bool     `json:"read_only"`                // Whether mount is read-only
}

// NamespaceMetadata stores namespace information
type NamespaceMetadata struct {
	Type       string `json:"type"`        // net, pid, mnt, etc.
	Inode      uint64 `json:"inode"`       // Namespace inode
	IsExternal bool   `json:"is_external"` // Whether namespace is external (shared)
}

// NewCheckpointMetadata creates a new metadata instance
func NewCheckpointMetadata(checkpointID string) *CheckpointMetadata {
	return &CheckpointMetadata{
		CheckpointID: checkpointID,
		CreatedAt:    time.Now().UTC(),
		Mounts:       make([]MountMetadata, 0),
		Namespaces:   make([]NamespaceMetadata, 0),
	}
}

// SaveMetadata writes metadata to a JSON file in the checkpoint directory
func SaveMetadata(checkpointDir string, meta *CheckpointMetadata) error {
	data, err := json.MarshalIndent(meta, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal metadata: %w", err)
	}

	metadataPath := filepath.Join(checkpointDir, MetadataFilename)
	if err := os.WriteFile(metadataPath, data, 0644); err != nil {
		return fmt.Errorf("failed to write metadata file: %w", err)
	}

	return nil
}

// LoadMetadata reads metadata from a checkpoint directory
func LoadMetadata(checkpointDir string) (*CheckpointMetadata, error) {
	metadataPath := filepath.Join(checkpointDir, MetadataFilename)

	data, err := os.ReadFile(metadataPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read metadata file: %w", err)
	}

	var meta CheckpointMetadata
	if err := json.Unmarshal(data, &meta); err != nil {
		return nil, fmt.Errorf("failed to unmarshal metadata: %w", err)
	}

	return &meta, nil
}

// SaveDescriptors writes file descriptor information to the checkpoint directory
func SaveDescriptors(checkpointDir string, descriptors []string) error {
	data, err := json.Marshal(descriptors)
	if err != nil {
		return fmt.Errorf("failed to marshal descriptors: %w", err)
	}

	descriptorsPath := filepath.Join(checkpointDir, DescriptorsFilename)
	if err := os.WriteFile(descriptorsPath, data, 0600); err != nil {
		return fmt.Errorf("failed to write descriptors file: %w", err)
	}

	return nil
}

// LoadDescriptors reads file descriptor information from checkpoint directory
func LoadDescriptors(checkpointDir string) ([]string, error) {
	descriptorsPath := filepath.Join(checkpointDir, DescriptorsFilename)

	data, err := os.ReadFile(descriptorsPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read descriptors file: %w", err)
	}

	var descriptors []string
	if err := json.Unmarshal(data, &descriptors); err != nil {
		return nil, fmt.Errorf("failed to unmarshal descriptors: %w", err)
	}

	return descriptors, nil
}

// GetCheckpointDir returns the path to a checkpoint directory
func GetCheckpointDir(baseDir, checkpointID string) string {
	return filepath.Join(baseDir, checkpointID)
}

// ListCheckpoints returns all checkpoint IDs in the base directory
func ListCheckpoints(baseDir string) ([]string, error) {
	entries, err := os.ReadDir(baseDir)
	if err != nil {
		return nil, fmt.Errorf("failed to read checkpoint directory: %w", err)
	}

	var checkpoints []string
	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}

		// Check if metadata file exists
		metadataPath := filepath.Join(baseDir, entry.Name(), MetadataFilename)
		if _, err := os.Stat(metadataPath); err == nil {
			checkpoints = append(checkpoints, entry.Name())
		}
	}

	return checkpoints, nil
}

// GetCheckpointInfo returns metadata for a specific checkpoint
func GetCheckpointInfo(baseDir, checkpointID string) (*CheckpointMetadata, error) {
	checkpointDir := GetCheckpointDir(baseDir, checkpointID)
	return LoadMetadata(checkpointDir)
}

// DeleteCheckpoint removes a checkpoint directory
func DeleteCheckpoint(baseDir, checkpointID string) error {
	checkpointDir := GetCheckpointDir(baseDir, checkpointID)
	return os.RemoveAll(checkpointDir)
}