Conservative SmartOS global-zone memory pressure guard with dry-run-first policy and SMF integration.
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

217 строки
10 KiB

  1. #include "guard.h"
  2. #include <assert.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <inttypes.h>
  6. static unsigned checks;
  7. #define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #x); exit(1); } checks++; } while (0)
  8. static const char *uuid = "11111111-1111-4111-8111-111111111111";
  9. static config_t allowed(void)
  10. {
  11. config_t c; defaults(&c);
  12. c.nzones = 1; strcpy(c.zones[0].uuid, uuid);
  13. strcpy(c.zones[0].alias, "fixture-web"); c.zones[0].policy = ALLOW;
  14. return c;
  15. }
  16. static process_t process(int pid)
  17. {
  18. process_t p; memset(&p, 0, sizeof (p));
  19. p.pid = pid; p.ppid = 100; p.zid = 9; p.initpid = 99; p.nlwp = 1;
  20. p.zone_generation = 20; p.start_sec = 1000; p.start_nsec = 123;
  21. p.rss_kib = 1048576; strcpy(p.uuid, uuid); strcpy(p.name, "php-fpm");
  22. return p;
  23. }
  24. static sample_t sample(double at, uint64_t free_pages, uint64_t scanner)
  25. {
  26. sample_t s; memset(&s, 0, sizeof (s)); s.at = at; s.free = free_pages;
  27. s.lots = 130875; s.des = 65437; s.min = 49077; s.scan = scanner;
  28. return s;
  29. }
  30. static void step(engine_t *e, config_t *c, double at, uint64_t free_pages, uint64_t scanner)
  31. {
  32. sample_t s = sample(at, free_pages, scanner); delta_t d = {0, 0, 0};
  33. engine_step(e, c, &s, d, d, d);
  34. }
  35. static int parse(const char *text, config_t *c)
  36. {
  37. FILE *f = tmpfile(); int result; char error[128];
  38. CHECK(f != NULL); CHECK(fputs(text, f) >= 0); rewind(f);
  39. result = config_read(f, c, error, sizeof (error)); fclose(f); return result;
  40. }
  41. static void configs(void)
  42. {
  43. config_t c;
  44. CHECK(parse("# defaults\n", &c) == 0 && !c.armed);
  45. CHECK(parse("ACTION_MODE dry-run\nprotect_process postgres\n", &c) == 0);
  46. CHECK(parse("ACTION_MODE armd\n", &c) != 0);
  47. CHECK(parse("ACTION_MODE armed\n", &c) != 0);
  48. CHECK(parse("ACTION_MODE dry-run\nACTION_MODE armed\n", &c) != 0);
  49. CHECK(parse("LOG_KEEP 0\n", &c) != 0);
  50. CHECK(parse("TERM_GRACE_SECONDS -1\n", &c) != 0);
  51. CHECK(parse("TERM_GRACE_SECONDS 1 garbage\n", &c) != 0);
  52. CHECK(parse("UNKNOWN foo\n", &c) != 0);
  53. CHECK(parse("PRE_SECONDS 20\nPRESSURE_SECONDS 10\n", &c) != 0);
  54. CHECK(parse("zone global global allow\n", &c) != 0);
  55. CHECK(parse("zone 11111111-1111-4111-8111-111111111111 web allow\nACTION_MODE armed\n", &c) == 0 && c.armed);
  56. CHECK(parse("zone 11111111-1111-4111-8111-111111111111 web allow\nzone 11111111-1111-4111-8111-111111111111 db protect\n", &c) != 0);
  57. CHECK(!uuid_valid("11111111-1111-4111-8111-11111111111g"));
  58. CHECK(!uuid_valid("00000000-0000-0000-0000-000000000000"));
  59. puts("PASS strict configuration, safe defaults, explicit UUID authorization");
  60. }
  61. static void protections(void)
  62. {
  63. config_t c = allowed(); process_t p = process(123), q;
  64. CHECK(!protected_process(&c, &p));
  65. q = p; q.zid = 0; CHECK(protected_process(&c, &q));
  66. q = p; q.pid = 1; CHECK(protected_process(&c, &q));
  67. q = p; q.pid = q.initpid; CHECK(protected_process(&c, &q));
  68. q = p; q.initpid = 0; CHECK(protected_process(&c, &q));
  69. q = p; q.zone_generation = 0; CHECK(protected_process(&c, &q));
  70. q = p; q.nlwp = 0; CHECK(protected_process(&c, &q));
  71. q = p; q.ppid = 0; CHECK(protected_process(&c, &q));
  72. q = p; strcpy(q.name, "sshd"); CHECK(protected_process(&c, &q));
  73. q = p; q.rss_kib = 100; CHECK(protected_process(&c, &q));
  74. q = p; q.uuid[0] = '2'; CHECK(protected_process(&c, &q));
  75. c.zones[0].policy = PROTECT; CHECK(protected_process(&c, &p));
  76. c.zones[0].policy = OBSERVE; CHECK(protected_process(&c, &p));
  77. c.zones[0].policy = ALLOW;
  78. strcpy(c.protected_names[c.nnames++], "php-fpm"); CHECK(protected_process(&c, &p));
  79. c.nnames = 0; c.protected_pids[c.npids++] = p.pid; CHECK(protected_process(&c, &p));
  80. q = p; CHECK(same_process(&p, &q));
  81. q.start_nsec++; CHECK(!same_process(&p, &q));
  82. q = p; q.zone_generation++; CHECK(!same_process(&p, &q));
  83. q = p; q.uuid[0] = '2'; CHECK(!same_process(&p, &q));
  84. puts("PASS mandatory protections, PID reuse and zone incarnation identity");
  85. }
  86. static void histories(void)
  87. {
  88. history_t h = {0}; delta_t d; unsigned i;
  89. for (i = 0; i <= 70; i++) history_add(&h, i, 10000 - 10 * i);
  90. d = history_delta(&h, 70, 5); CHECK(d.valid && d.value == -50 && d.age == 5);
  91. d = history_delta(&h, 70, 15); CHECK(d.valid && d.value == -150);
  92. d = history_delta(&h, 70, 60); CHECK(d.valid && d.value == -600);
  93. CHECK(!history_delta(&h, 90, 5).valid);
  94. memset(&h, 0, sizeof (h));
  95. history_add(&h, 0, 123); history_add(&h, 15, 100);
  96. CHECK(!history_delta(&h, 15, 5).valid);
  97. CHECK(history_delta(&h, 15, 15).value == -23);
  98. d.valid = 1; d.value = 100;
  99. CHECK(rank_score(1000, d) == 1200);
  100. CHECK(rank_score(UINT64_MAX - 10, d) == UINT64_MAX);
  101. d.value = -100; CHECK(rank_score(1000, d) == 1000);
  102. {
  103. uint64_t bytes = 0;
  104. CHECK(swap_physical_bytes_from_blocks(67024888, 58931824, &bytes) == 0);
  105. CHECK(bytes == UINT64_C(4143648768));
  106. CHECK(swap_physical_bytes_from_blocks(10, 11, &bytes) != 0);
  107. CHECK(swap_physical_bytes_from_blocks(UINT64_MAX, 0, &bytes) != 0);
  108. }
  109. puts("PASS timestamped 5/15/60s windows, missing data, signed growth, overflow");
  110. }
  111. static void states(void)
  112. {
  113. config_t c = allowed(); engine_t e; unsigned i;
  114. engine_init(&e);
  115. for (i = 0; i < 10; i++) step(&e, &c, i, 425950, 0);
  116. CHECK(e.state == NORMAL && !e.danger);
  117. for (i = 10; i <= 12; i++) step(&e, &c, i, 100000, 1);
  118. CHECK(e.state == NORMAL);
  119. step(&e, &c, 13, 100000, 1); CHECK(e.state == PRE_PRESSURE);
  120. for (i = 14; i <= 20; i++) step(&e, &c, i, 100000, 1);
  121. CHECK(e.state == PRESSURE && !e.danger);
  122. step(&e, &c, 21, 60000, 1); step(&e, &c, 22, 59000, 1);
  123. CHECK(!e.danger);
  124. step(&e, &c, 23, 58000, 1); CHECK(e.state == CRITICAL && e.danger);
  125. step(&e, &c, 24, 140000, 0); CHECK(e.state == RECOVERY && !e.danger);
  126. for (i = 25; i <= 40; i++) step(&e, &c, i, 140000, 0);
  127. CHECK(e.state == RECOVERY); /* below 10% margin */
  128. for (i = 41; i <= 56; i++) step(&e, &c, i, 150000, 0);
  129. CHECK(e.state == NORMAL);
  130. step(&e, &c, 57, 40000, 0); CHECK(e.state == EMERGENCY && e.danger && !e.floor);
  131. step(&e, &c, 58, 20000, 0); CHECK(e.floor);
  132. engine_init(&e);
  133. for (i = 0; i < 20; i++) step(&e, &c, i, 60000, 0);
  134. CHECK(!e.danger); /* low but stable, no scanner */
  135. engine_init(&e);
  136. step(&e, &c, 0, 60000, 1); step(&e, &c, 20, 59000, 1);
  137. CHECK(!e.danger); /* gaps do not count as sustained samples */
  138. engine_gap(&e); step(&e, &c, 21, 58000, 1); CHECK(!e.danger);
  139. step(&e, &c, 22, 57000, 1); step(&e, &c, 23, 56000, 1); CHECK(e.danger);
  140. {
  141. sample_t s = sample(1, 60000, 0);
  142. delta_t none = {0, 0, 0}, rising = {1, 5, 4096};
  143. engine_init(&e); engine_step(&e, &c, &s, none, rising, none);
  144. CHECK(!e.danger && e.critical_since < 0); /* physical swap alone is inert */
  145. engine_init(&e); engine_step(&e, &c, &s, none, none, rising);
  146. CHECK(!e.danger && e.critical_since < 0); /* pageout alone is inert */
  147. s.free = 150000;
  148. engine_init(&e); engine_step(&e, &c, &s, none, rising, rising);
  149. CHECK(e.state == NORMAL && e.low_since < 0); /* corroboration needs low memory */
  150. s.free = 60000;
  151. engine_init(&e);
  152. for (i = 0; i <= 3; i++) {
  153. s.at = i; engine_step(&e, &c, &s, none, rising, rising);
  154. }
  155. CHECK(e.state == CRITICAL && e.danger);
  156. }
  157. puts("PASS all six states, corroboration, hysteresis, recovery and sampling gaps");
  158. }
  159. static void actions(void)
  160. {
  161. config_t c = allowed(); engine_t e; controller_t a = {0};
  162. process_t p = process(123); sample_t s; plan_t plan; unsigned i;
  163. engine_init(&e); e.state = CRITICAL; e.danger = 1;
  164. s = sample(100, 60000, 1);
  165. plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == TERM);
  166. action_commit(&a, &c, &s, &plan); CHECK(a.has_pending && a.nvictims == 1);
  167. printf("SIMULATED t=100 state=CRITICAL UUID=%s pid=123 WOULD_ACTION=SIGTERM\n", uuid);
  168. s.at = 104; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == NONE);
  169. s.at = 105; plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL);
  170. action_commit(&a, &c, &s, &plan); CHECK(!a.has_pending && a.nvictims == 1);
  171. puts("SIMULATED t=105 state=CRITICAL pid=123 WOULD_ACTION=SIGKILL reason=memory_not_recovered");
  172. p.pid++; s.at = 106; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE);
  173. e.state = EMERGENCY; s.free = 40000;
  174. plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == TERM);
  175. action_commit(&a, &c, &s, &plan);
  176. s.at = 106.5; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE);
  177. s.at = 107; CHECK(action_plan(&a, &c, &e, &s, &p).action == KILL);
  178. s.free = 20000; e.floor = 1;
  179. plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL);
  180. action_commit(&a, &c, &s, &plan);
  181. p.pid++; s.at++;
  182. plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL);
  183. action_commit(&a, &c, &s, &plan);
  184. puts("SIMULATED state=EMERGENCY floor=1 WOULD_ACTION=SIGKILL reason=floor");
  185. p.pid++; s.at++;
  186. plan = action_plan(&a, &c, &e, &s, &p); CHECK(plan.action == KILL);
  187. action_commit(&a, &c, &s, &plan);
  188. p.pid++; s.at++;
  189. CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); /* even at floor */
  190. e.state = NORMAL; e.danger = 0; s.free = 160000;
  191. (void)action_plan(&a, &c, &e, &s, &p); CHECK(a.nvictims == 0);
  192. e.state = EMERGENCY; e.danger = 1; s.free = 20000;
  193. CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE); /* minute budget persists */
  194. s.at = 180; CHECK(action_plan(&a, &c, &e, &s, &p).action == KILL);
  195. memset(&a, 0, sizeof (a)); e.floor = 0; e.state = CRITICAL; s.free = 60000;
  196. plan = action_plan(&a, &c, &e, &s, &p); action_commit(&a, &c, &s, &plan);
  197. s.free = 140000; e.danger = 0; e.state = RECOVERY;
  198. CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE && !a.has_pending);
  199. for (i = 0; i < 2; i++) {
  200. c.armed = (int)i; memset(&a, 0, sizeof (a));
  201. e.danger = 1; e.state = EMERGENCY; e.floor = 1; s.free = 1000;
  202. p.zid = 0; CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE);
  203. p.zid = 9; c.zones[0].policy = PROTECT;
  204. CHECK(action_plan(&a, &c, &e, &s, &p).action == NONE);
  205. c.zones[0].policy = ALLOW;
  206. }
  207. puts("PASS TERM/KILL sequencing, emergency grace, floor, cooldown, budgets and no-action protections");
  208. }
  209. int main(void)
  210. {
  211. configs(); protections(); histories(); states(); actions();
  212. printf("PASS %u checks; no native backend linked, no signals sent\n", checks);
  213. return 0;
  214. }