github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/systemd/freeze_test.go (about) 1 package systemd 2 3 import ( 4 "bufio" 5 "bytes" 6 "os" 7 "os/exec" 8 "strings" 9 "testing" 10 11 "github.com/opencontainers/runc/libcontainer/cgroups" 12 "github.com/opencontainers/runc/libcontainer/configs" 13 "golang.org/x/sys/unix" 14 ) 15 16 func TestFreezeBeforeSet(t *testing.T) { 17 requireV1(t) 18 19 testCases := []struct { 20 desc string 21 // Test input. 22 cg *configs.Cgroup 23 preFreeze bool 24 // Expected values. 25 // Before unit creation (Apply). 26 freeze0, thaw0 bool 27 // After unit creation. 28 freeze1, thaw1 bool 29 }{ 30 { 31 // A slice with SkipDevices. 32 desc: "slice,skip-devices", 33 cg: &configs.Cgroup{ 34 Name: "system-runc_test_freeze_1.slice", 35 Parent: "system.slice", 36 Resources: &configs.Resources{ 37 SkipDevices: true, 38 }, 39 }, 40 // Expected. 41 freeze0: false, 42 thaw0: false, 43 freeze1: false, 44 thaw1: false, 45 }, 46 { 47 // A scope with SkipDevices. Not a realistic scenario with runc 48 // (as container can't have SkipDevices == true), but possible 49 // for a standalone cgroup manager. 50 desc: "scope,skip-devices", 51 cg: &configs.Cgroup{ 52 ScopePrefix: "test", 53 Name: "testFreeze2", 54 Parent: "system.slice", 55 Resources: &configs.Resources{ 56 SkipDevices: true, 57 }, 58 }, 59 // Expected. 60 freeze0: false, 61 thaw0: false, 62 freeze1: false, 63 thaw1: false, 64 }, 65 { 66 // A slice that is about to be frozen in Set. 67 desc: "slice,will-freeze", 68 cg: &configs.Cgroup{ 69 Name: "system-runc_test_freeze_3.slice", 70 Parent: "system.slice", 71 Resources: &configs.Resources{ 72 Freezer: configs.Frozen, 73 }, 74 }, 75 // Expected. 76 freeze0: true, 77 thaw0: false, 78 freeze1: true, 79 thaw1: false, 80 }, 81 { 82 // A pre-frozen slice that should stay frozen. 83 desc: "slice,pre-frozen,will-freeze", 84 cg: &configs.Cgroup{ 85 Name: "system-runc_test_freeze_4.slice", 86 Parent: "system.slice", 87 Resources: &configs.Resources{ 88 Freezer: configs.Frozen, 89 }, 90 }, 91 preFreeze: true, 92 // Expected. 93 freeze0: true, // not actually frozen yet. 94 thaw0: false, 95 freeze1: false, 96 thaw1: false, 97 }, 98 { 99 // A pre-frozen scope with skip devices set. 100 desc: "scope,pre-frozen,skip-devices", 101 cg: &configs.Cgroup{ 102 ScopePrefix: "test", 103 Name: "testFreeze5", 104 Parent: "system.slice", 105 Resources: &configs.Resources{ 106 SkipDevices: true, 107 }, 108 }, 109 preFreeze: true, 110 // Expected. 111 freeze0: false, 112 thaw0: false, 113 freeze1: false, 114 thaw1: false, 115 }, 116 { 117 // A pre-frozen scope which will be thawed. 118 desc: "scope,pre-frozen", 119 cg: &configs.Cgroup{ 120 ScopePrefix: "test", 121 Name: "testFreeze6", 122 Parent: "system.slice", 123 Resources: &configs.Resources{}, 124 }, 125 preFreeze: true, 126 // Expected. 127 freeze0: true, // not actually frozen yet. 128 thaw0: true, 129 freeze1: false, 130 thaw1: false, 131 }, 132 } 133 134 for _, tc := range testCases { 135 tc := tc 136 t.Run(tc.desc, func(t *testing.T) { 137 m, err := NewLegacyManager(tc.cg, nil) 138 if err != nil { 139 t.Fatal(err) 140 } 141 defer m.Destroy() //nolint:errcheck 142 143 // Checks for a non-existent unit. 144 freeze, thaw, err := m.freezeBeforeSet(getUnitName(tc.cg), tc.cg.Resources) 145 if err != nil { 146 t.Fatal(err) 147 } 148 if freeze != tc.freeze0 || thaw != tc.thaw0 { 149 t.Errorf("before Apply (non-existent unit): expected freeze: %v, thaw: %v, got freeze: %v, thaw: %v", 150 tc.freeze0, tc.thaw0, freeze, thaw) 151 } 152 153 // Create systemd unit. 154 pid := -1 155 if strings.HasSuffix(getUnitName(tc.cg), ".scope") { 156 // Scopes require a process inside. 157 cmd := exec.Command("bash", "-c", "sleep 1m") 158 if err := cmd.Start(); err != nil { 159 t.Fatal(err) 160 } 161 pid = cmd.Process.Pid 162 // Make sure to not leave a zombie. 163 defer func() { 164 // These may fail, we don't care. 165 _ = cmd.Process.Kill() 166 _ = cmd.Wait() 167 }() 168 } 169 if err := m.Apply(pid); err != nil { 170 t.Fatal(err) 171 } 172 if tc.preFreeze { 173 if err := m.Freeze(configs.Frozen); err != nil { 174 t.Error(err) 175 return // no more checks 176 } 177 } 178 freeze, thaw, err = m.freezeBeforeSet(getUnitName(tc.cg), tc.cg.Resources) 179 if err != nil { 180 t.Error(err) 181 return // no more checks 182 } 183 if freeze != tc.freeze1 || thaw != tc.thaw1 { 184 t.Errorf("expected freeze: %v, thaw: %v, got freeze: %v, thaw: %v", 185 tc.freeze1, tc.thaw1, freeze, thaw) 186 } 187 // Destroy() timeouts on a frozen container, so we need to thaw it. 188 if tc.preFreeze { 189 if err := m.Freeze(configs.Thawed); err != nil { 190 t.Error(err) 191 } 192 } 193 // Destroy() does not kill processes in cgroup, so we should. 194 if pid != -1 { 195 if err = unix.Kill(pid, unix.SIGKILL); err != nil { 196 t.Errorf("unable to kill pid %d: %s", pid, err) 197 } 198 } 199 // Not really needed, but may help catch some bugs. 200 if err := m.Destroy(); err != nil { 201 t.Errorf("destroy: %s", err) 202 } 203 }) 204 } 205 } 206 207 // requireV1 skips the test unless a set of requirements (cgroup v1, 208 // systemd, root) is met. 209 func requireV1(t *testing.T) { 210 t.Helper() 211 if cgroups.IsCgroup2UnifiedMode() { 212 t.Skip("Test requires cgroup v1.") 213 } 214 if !IsRunningSystemd() { 215 t.Skip("Test requires systemd.") 216 } 217 if os.Geteuid() != 0 { 218 t.Skip("Test requires root.") 219 } 220 } 221 222 func TestFreezePodCgroup(t *testing.T) { 223 if !IsRunningSystemd() { 224 t.Skip("Test requires systemd.") 225 } 226 if os.Geteuid() != 0 { 227 t.Skip("Test requires root.") 228 } 229 230 podConfig := &configs.Cgroup{ 231 Parent: "system.slice", 232 Name: "system-runc_test_pod.slice", 233 Resources: &configs.Resources{ 234 SkipDevices: true, 235 Freezer: configs.Frozen, 236 }, 237 } 238 // Create a "pod" cgroup (a systemd slice to hold containers), 239 // which is frozen initially. 240 pm := newManager(t, podConfig) 241 if err := pm.Apply(-1); err != nil { 242 t.Fatal(err) 243 } 244 245 if err := pm.Set(podConfig.Resources); err != nil { 246 t.Fatal(err) 247 } 248 249 // Check the pod is frozen. 250 pf, err := pm.GetFreezerState() 251 if err != nil { 252 t.Fatal(err) 253 } 254 if pf != configs.Frozen { 255 t.Fatalf("expected pod to be frozen, got %v", pf) 256 } 257 258 // Create a "container" within the "pod" cgroup. 259 // This is not a real container, just a process in the cgroup. 260 containerConfig := &configs.Cgroup{ 261 Parent: "system-runc_test_pod.slice", 262 ScopePrefix: "test", 263 Name: "inner-container", 264 Resources: &configs.Resources{}, 265 } 266 267 cmd := exec.Command("bash", "-c", "while read; do echo $REPLY; done") 268 cmd.Env = append(os.Environ(), "LANG=C") 269 270 // Setup stdin. 271 stdinR, stdinW, err := os.Pipe() 272 if err != nil { 273 t.Fatal(err) 274 } 275 cmd.Stdin = stdinR 276 277 // Setup stdout. 278 stdoutR, stdoutW, err := os.Pipe() 279 if err != nil { 280 t.Fatal(err) 281 } 282 cmd.Stdout = stdoutW 283 rdr := bufio.NewReader(stdoutR) 284 285 // Setup stderr. 286 var stderr bytes.Buffer 287 cmd.Stderr = &stderr 288 289 err = cmd.Start() 290 stdinR.Close() 291 stdoutW.Close() 292 defer func() { 293 _ = stdinW.Close() 294 _ = stdoutR.Close() 295 }() 296 if err != nil { 297 t.Fatal(err) 298 } 299 // Make sure to not leave a zombie. 300 defer func() { 301 // These may fail, we don't care. 302 _ = cmd.Process.Kill() 303 _ = cmd.Wait() 304 }() 305 306 // Put the process into a cgroup. 307 cm := newManager(t, containerConfig) 308 309 if err := cm.Apply(cmd.Process.Pid); err != nil { 310 t.Fatal(err) 311 } 312 if err := cm.Set(containerConfig.Resources); err != nil { 313 t.Fatal(err) 314 } 315 // Check that we put the "container" into the "pod" cgroup. 316 if !strings.HasPrefix(cm.Path("freezer"), pm.Path("freezer")) { 317 t.Fatalf("expected container cgroup path %q to be under pod cgroup path %q", 318 cm.Path("freezer"), pm.Path("freezer")) 319 } 320 // Check the container is not reported as frozen despite the frozen parent. 321 cf, err := cm.GetFreezerState() 322 if err != nil { 323 t.Fatal(err) 324 } 325 if cf != configs.Thawed { 326 t.Fatalf("expected container to be thawed, got %v", cf) 327 } 328 329 // Unfreeze the pod. 330 if err := pm.Freeze(configs.Thawed); err != nil { 331 t.Fatal(err) 332 } 333 334 cf, err = cm.GetFreezerState() 335 if err != nil { 336 t.Fatal(err) 337 } 338 if cf != configs.Thawed { 339 t.Fatalf("expected container to be thawed, got %v", cf) 340 } 341 342 // Check the "container" works. 343 marker := "one two\n" 344 _, err = stdinW.WriteString(marker) 345 if err != nil { 346 t.Fatal(err) 347 } 348 reply, err := rdr.ReadString('\n') 349 if err != nil { 350 t.Fatalf("reading from container: %v", err) 351 } 352 if reply != marker { 353 t.Fatalf("expected %q, got %q", marker, reply) 354 } 355 }