Conservative SmartOS global-zone memory pressure guard with dry-run-first policy and SMF integration.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

217 lignes
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. }