diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go index 4fec097373..6f96f1a333 100644 --- a/packages/api/internal/api/api.gen.go +++ b/packages/api/internal/api/api.gen.go @@ -988,6 +988,9 @@ type SandboxRefreshRequest struct { // SandboxSnapshotRequest defines model for SandboxSnapshotRequest. type SandboxSnapshotRequest struct { + // Memory Whether the snapshot template captures the sandbox's memory. When false, the template persists only the filesystem (a filesystem-only snapshot): sandboxes created from it cold-boot (reboot) from disk instead of restoring in-memory state. The source sandbox itself is unaffected either way — it is checkpointed with memory and resumes with its processes and memory intact. Defaults to true. + Memory *bool `json:"memory,omitempty"` + // Name Optional name for the snapshot template. If a snapshot template with this name already exists, a new build will be assigned to the existing template instead of creating a new one. Name *string `json:"name,omitempty"` } @@ -13765,196 +13768,198 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // const string: with thousands of chunks the chained `+` fold is several // times slower for the Go compiler than parsing a slice literal. var swaggerSpec = []string{ - "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", - "AwAA//8=", + "7H3tUhw5tuCrKGpvxLVniwLj7onbTMwPDO4ebmObANw9d9veviLzVJXG+TWSEqhxELEPsU+4T7KhIylT", + "man8KqAANzER06ZSn0fnHB2dz6+TII2zNIFEisne10lGOY1BAse/aBCAEOfpF0iODtUPLJnsTTIql5Pp", + "JKExTPZqbaYTDv/MGYdwsid5DtOJCJYQU9VZrjLVQUjOksXk5mY6oRn7GVbtQ9vP40a9yFkUtg5qv44b", + "M0lDaB3SfBw3YkYXLKGSpckxi5lUjUIQAWeZ+m2yN3lHr1mcxyTJ4wvgJJ0TJiEWRKaEg8x5QjLgJKML", + "mEz1qv6ZA1+Vy4pwXHcVIcxpHsnJ3qudnelknvKYysnehCXy9e5kOon1jOZzzBLz19QunyUSFsBr638P", + "1xLPv7mHg5yLlKslC0m5JHIJJGJCkjlP45ZlJ8Vw3QAUNAkv0uvWUym/jzsYCTRuHdR8HDtinEVUQseo", + "RYNxI1+mUR63j1t8HjPqjWossjQRgEzgu50d9Z8gTSQkiKc0yyIW4Nlv/0OkeO7leP/GYT7Zm/yP7ZKz", + "bOuvYvst5ynXc1QR5Q0NiVoiCDm5mU6+23l1/3Pu53IJiTSjEtDt1OSv73/yH1N+wcIQEj3jd/c/4/tU", + "knmaJ6Ge8Yf7n/EgTeYRC/SJvtoAFv2UJqAm+34TKHsG/BK4RZsbS1JIM/u/np3CggnJV3ir8jQDLpkm", + "KHol9vHSVJdb2GSa+7+eEd2A/AwrcnRI5iknbw9OCa1g7GRap92pGltNnCb+YfU3crUEDsiM1ajcrJQw", + "QaI0oBLClqHPIOAgi8X759CN3B0MX77+oT7q+SoDdf8VC20MBIm6qH5Ta5x8nnoYZcn+ftNfp/Vj8G7Q", + "BWg5bnrxD9BYvR/GLHmjJIoDmgQQnYLA+7V+5AF+jSA8SPPEc9e/L+54FE8EETmuYZ5H0YoUvSfNm3g6", + "mVM2YmC5pJLoLupa1kNPvDe8C7PaBqqzfraQONNX7s8saoXEwNWayxsaC/7CosgLBvVh1MAVEOve/XBw", + "Z/EAQQi2SM7NbX5OF+LU3GkNOEi6EB5MpwsU8CgOpP6liNSKB0pgUiKg59YuFk45pyv8m/IFSN8U6vdi", + "TMIS8gnlhT1JF58mxEiFvUSkh5/qjZSbh9DdfnPfjnBeXddRqCh6zvQxqW1jUwWKNGCKKZErJpfqiwCC", + "szoibJ4zL9Pyg9kuFYex060B5QZMcFF2iwooyBuO08XbxHsVRHAJUd8NdJwujrHdzXQSgxBK4m9s6Thd", + "EPOR2HvPAw8hIWt2PpOQKUQooZ7xFNk3hwhBbzAxShcEcCs+WLMYhKSxZ4Jz+8kC2x2oOMSQSthSo/Rj", + "XzFVCZKpgWYB9jNJZS5OgZr7vgZ6fSjmr+Jl9NvnqQeyoFvWwSFwBsL1FA7edB1nFSU8lNt6xu/M+Vo6", + "qM4/JUHOOSQyWhEOWcolSxYkTSJ9AaOcYnqMxAyHBfeejF28OoWDk48t/Pjg5CMJUg4Cl4Zb0Xx54nuW", + "djxEp0rITCCQ5urxMFoWQ5pLP06muVR4LyBIk1DgqxRXYyBJVGdC5xI4uVqyYOkulYhlmkchgeuMcehc", + "+E7vvWJX6RMyDjgopNsvFS0eAcO0kT20p7U1RKpRCHbSAtQQGpxOWDiEb7tzDOHRMRVf+oimnOUdFV9Y", + "sjgESVkkVH/92G1c+TSGlhU1OZdfe3G+BGIkMA3enoFqZ4q7xcXZGcxep85xfS4P+BxovH9yZATr9c53", + "/+SIfIHV+KM1E7zBuWkUfZhP9n7rPhO13o9CIfPn6STJo4heRKD1C4Nxxax3CJp88T04TukVuaRRDs0B", + "GwNEVMiPAjzrOqbC0LpcMlEA8YoKkgtkel4gVvf8IJjdul0fLuqGBgUNYlYx8RAikDBIgO1fmyNQDZTL", + "rPgb4jLWF8Qs0VnR9JCJL+9AchZ4JNIQLlng2coh/k7sWPUFzFkEYiUkxOfeR+uPxXei+pIXMFvMpgSu", + "5XdTcj0XL72sUF2XJynz3Znv1DeSqY8WwiHDo/TwM0mjNysJPhirb0RkNEDZ/wJbueTHEvnn77xPLEUL", + "LaMqulpn0Lr0UO5/ag+mAWp3IZW92qM+Y/+Cd288J8rEFyLYv6Audag1v2Nvxt7h08nb5PIXamwlYcjU", + "PDQ6qaGXu4S3ySXjaRIr4eKScqbYh08IalLz2+Qy/AW48Op2zAeLF5BchoTnSaIkQCPXt449nWgVV/PO", + "SUMPXmNjgt884GqCqFWa1bP2MS4zkStW/sjT+CimC3BVbCFTY8csoVLvJaZZpgbUCrc27usq6qaTRZC1", + "Nfzp4MRpyIuZW1pDApxGRY+bqYXt6r1R+atd30wnaQIDrlp3mTfT7rbuSnvb1tep4OsO0EAKAVxR5X4Q", + "KFL9T+HDxjPdhphG5D/PPrxHHP/p4GQDSkB1ikOVgJ7t+ETwOpwaYMmoEFcp98gWJ+aLutdyUbIeXmLT", + "nUOgGPuzZ/BcAPdf3h/Nl+FL9QO1mGFawsUH1VbRpwFeJbNA+IsS9E44zNm1B874O8priuXpHuSyyhj1", + "uyflbSKiM89ZPvfOo3+/5TxZ9ybwwc0sdERjSGIA3RgXReFjSBZy6ZFy8ffuJbZdzGbB1RmmnnPxwVAx", + "lWMmJIStr3QaMepT1Kmfh8iTQcQgkVavmHHQZgwjmPe9QnRv77hZXqgwuhhpoeq4maqryBFBuno5wsqN", + "ot7W9x25WkLlGidXLIo8qofONx5URYhOq5fTFC/xOOWr/g29s+2wj6Qhlb0GNoMT72zzumm/7/A6BBt0", + "OoAxUKWCmE6DoSqkwslhmzzDtg2XgL4tFsp6VFBpTRQTlZWbd5yXKaAXAD4fkMQGaSnNgn8p+/arv10v", + "Btf7oiBO90Qc2nLwq0I9liQsjKsYjFzFqsY9iksFrwaK2BsyhIt8gQ4o83QynVxRjvcniqS+S/M4XYhD", + "xiGQXvm7+OTot43pymgJL8B47eAZ2WXMU35FufrlggZf8J+N2aeT6y3VfuuS4q0qVMfKen4sRqn8/KYY", + "0mzgLM2576Wrfx+5dHXaKacoFWTqSATaHIYvX8967gxT/nriDHgznbyjwZIlcKQOq/lMyfJ9HiyZhEDm", + "HPzKZuq0sBtN9NPCx/N/pDGLVv6h5vhtwCDv0tCHmWqMWH0aOsR7r7BWDpM4Ohf/WPU3VbFBZ521+aYN", + "uOqDuD4HGmtdioepAo1JjB+NkcKx0zTV8o6xqPvGbpiPzBxjLEiOfepj4pO9OidRop7qprWEL6zBQLAk", + "AAJZGixf1p7DLToUlJ/8qmbjflfVZxqnKAjtcsxzfsEuISFqYH5JHYu49hbsNJhV4WCXhMcbZB2qjIYH", + "zLuDExKkyZwtcq59qJqKjBYdafkIeOeIFnVzl/qyjq7m1e5/+GD/Hq46jSi3NST4tJCf9bwdgm+UXv2O", + "55iA/F1P4BOEo/SqAIFMi5UsgdjOM/KrkmcESNVgTiMBU8IkuYAlvQQrLsRAlJCTQcDmK5YsSAjJ6kOO", + "fXZm+L/tHYtlCcirlH8xpzwrt3yRphFQlA1pLtMTmguo2FH19E2PuzSm6sEaRSuSqU5VKUab2lDkMQax", + "zhk1hlTm9bGPgzSRPI3MVAnNxDKV5AtLQiKpegU15EA1w5ZZX5rYxZAXaE3lEMElTaTuViwGJTKew0tz", + "DuYAEG2K4UjI08we25YWe9AoC4QmITEXqdBmW9Wq1H2SF9T5awtb2M28/AvhIPIY9X2SBGkUbl2kqRTk", + "BQf8x8vK/lCOVJLWjJzlwZLQEiwBTZJUIY1eNQ4LIblYEcnpfM4CXGicC6kFA/0ZrrOIBUxGqykRKa6h", + "GCdI4wuWWP8NNeop9pqRQ31qqPNXoCMv5nkUEQsWu7l2vNMDDZRl94sOB4jO5klkFbg9zyFspp41mkd2", + "voKC7JYPIEN3A3u+163LXQkIvJLQGf5OaBQRg1NBGsd5Yl1g8YAa7ykH5uOeLZYZdluCXPu8dU//3icB", + "KCyJ2KVXe28u5Nl4Ff4DvI7MndBl7707y597C+n1rjObhhIq9KS6bSZ7k//9G9361/7W/9rZ+uH3rc//", + "898GrsQjArw3hoaaXB/lQgIfhmqmsVeMTmNvfMUB/m4HSHmwBCE5mg9a7eM/WvVkj3uheY6j08xQ65ru", + "cqa9EmHMLKLoM2ymYab5tmdJXH2MdTJCp6lmiNYE29VLoYO11pbKoBGendbylSbuRiqQaTFWiUJNg95T", + "/XOahuTMTl5jQP5ZtNHhKBGSJoGXmVoTCjNtSm1w7/kYF68BQNYOcmWfgyVNFkPUZWpq63x2RQWJqJAk", + "0L0HP8cuBxozuynT5+fQhO/U4SYFhJrbriFbibFN6qxyhBbMKTdZsKEq/Xw23E8bPnx+48ESQvQY9DCE", + "YyaQf+lW1sObhTXEH+4z/Mxyn1nuxlnuMzN8Isywwoz6OaKP9RXs1McEHQ+teuRaaLVxoqFIpJGJWTo4", + "+diFq0U7UjgfD8TQoqdWN7R4QO2j71J1JvOQHelm5docfb5bZUBw6UY9nu6CLD8BHoCXwhXA1eA5+ptn", + "up12sh8ydsjEF+HzqJM6jsecpfZLp8ESVRHbcengNtSX3nXs89xny3wBJ3QBZ+xf0HJs6hMeGhEsWURA", + "VJ9M7Xrkqdm5xCkokvO5mZbIyE2bYjZBXmgqkXiRS5KkkqxAEnwRQ/hy7CoQVfoxKONQomu5mA506pvY", + "72Fbbr2chCXoZfICPfjIFplzgKH71JR13uvnmGjWsQ4Z6l4f230e3ztjW3eQtT0fK2yshedUiLa5QI9F", + "1QGQpcoGjtTPzofKPlKybPusuGKbFthcuBf07FOyRUJOmRIV9srLkwlykeYJukhdABHLXJIwvUpm5Ehq", + "RwdFDTQIIJMkgStH7qBJqFsImWYkVcIBRccIJvBd5rTkQMI00YtQd2F4saquQU8i2SVE+kCnSIlal2lz", + "LmD2BRqucOYgTSRLciB4ySYLqyKdfap6Y9FwpeBvdo43Igat6D/yZAk0ksuVvoTVwgaaUUvwn5o5yl8O", + "y9nKHw/cecufPzorKH89s2upHLQWEO5MXdPr9j9egqtRlhlA7UJrfzu8kKqmjG6j5B0ZMx5W/6mA9eSc", + "ssI0pswjK7+hQtG4+uiEuBdGM2O+YMIYz9hFNCiKA5LLevBVDSBuUBXeBCjYJJdhVb9+tz5Zd+UktUlX", + "JHMGndDEn0vbgQKlOa+Sn5NLRknG0+vVrP8E13BTqvsZtVmQmqhQ2sw8pnFkEmF5Kc0arxlI1EbC0Rat", + "t6ZffbN2PJ/avXWQQXZcu0szA5lHdOHfpDXzaYuo35hn1tKmB7stJ0Ir+5Gxk++32Nh/XYJcAi/s6dbG", + "rh71pX2z2HDK1bPFbL7KkWfkvbZj0kQo+UGNoKQLZxQBsgN1Hch8C56tG2fYG3CkfYQ3QsTmEKyCaKhB", + "/Lhov3kX39tauJ89hJ89hId4CJtVvl1wEOJESQxtV/fZh4Ofz77XUgWSqwUcYN8Z+ZBL/Uw9PzhB4OZJ", + "ApiWZsnTfKGTA+juWvTHW2c7hGRF5iySoMD9l6rkLkie0CvKYUYOkQ9uxVSiNWcepVcYfkw4xKkEcvj+", + "jLzYP/+vk79qjvnSd3/Urs0w5N6rrrJX00o9I5apkHtZyqVJA6OjVsmniZa14JrGWQSzII33Xu38x86n", + "ieub44S3tEZDfci0YyGxK7DRUS9Ofzwgr37Y/eHllMT0mux+/71Wpsx0YkIb1LL7/fejYprqE9qWt5qw", + "rrsxYO4Qso5d1lw7C4MLBfcmWRqxYFU4XZOLlfPUm6dNqbHqCuWXaiqsgCaud5dfJEuT8/JpOoARfCja", + "N+BTLs8dtgtc6cKf4kY741Z9i1EVE7EEGnDBH73jqC9deXIeKJcNLvhzBQ4tmYPmDIxNuC3wuM3aWwJ7", + "49mHHgqquH43U5CBXhXSoj9JUFW/yXP0jA91yERD2Bhzi3blA4pSX66E47uYs/fGxrmnLhxqMPtl99Qk", + "C/VCry+5UsH81G5EAdE7g55vO84O3jkC7LAAftujV7asTOKNlnjnxhcMZWntxsH3TbPgsAj9IMs/CghP", + "gpZETV1GwHmUusnibPSBFtI6bVAhJmNozRjRbpdRHf1GJszv0GqJ6bT0HNBg6Quy0Z41xsjzAu2CaAZ/", + "OX6KTmh0mKg6B/UD4l2PUap9yD9mWM6IYBnnSeLQTXkWzlE7iOVgrUsaDieqvnD9YSIffGnMrBMYtoCQ", + "hCCkScxtDOz4fClMU+QtDZYGekoOvABCycHR4Sm5iNLgSyHx/8cM/7f9evfT5OWUUHJBOZCjk+K5UGuI", + "rVJOqFXoaCnbNHJeDp8mU/Jp8qdZ5aeXM7JvNmBz7dHoiq4ERmgQhYcQgjrV9BI4CSFhZdPZKCc3BNRJ", + "fhGx4FzDpDeA5ExHyxBW4fnk4+mxcIIkSyWVDt+w4Q5Ojga/pG0icNrP1my3PCWhIF2eBfhP+rA8CG3/", + "TFJJRJ6pB55RSeHjlOfRWCBC+Z4eeEc3X+Am34LJMPW3VHggYCGvHqaYa8HoNlBrdwGlbg1jGsy5mJg5", + "b5pD3GvXdT9G7jBEe5prFVodSLXcDsC3DGlIThOhuJIGPcHs1DqtJD7/WbKwh/m38/OTbfV/Z8W2ZuRn", + "WFmDthqvpEWasVmD1BqEZik0wpwMBBWv1v5dMWpanrKFegjLg9QyM+Ax0/n8Kzbv2hvjpv2J58KuyfQr", + "AHLhY8BiYVHCq+A7JkKh+UouoD7udIu1DNnOuTtHy54Mb+449GJ3FzBPORp8rygPWbJo7moJNAQ+7iVY", + "XZjCLmKGUathidqb4jCK13IWgk50YtZYouF+Ujpa6P5OalLF/fEKYGo/WUQDCGcE04Jo3M0idVp6UeIv", + "ROi04RxEGuVoMVvSLINEGBvGllALMQARkITob5HaWJg10e9jpmSTNpXg+0r4o7Xk5dhHc53C8bWw/Jzq", + "vYpKHlF7rkjtBYwynl6yEMLq+DPyIWZSapzGhyoJIqBcECZnXvfHZ8Hg7gSDJx2K+8cVJB7jte7wgopP", + "muECCkEsB5iN4l0fXM1s3fCILKolrBkfMyTNkY9Y9zSTUR/N9t7cJ2ZWdIxqTQcaDwvFLrTRKQlohtk4", + "KPEF/VaDqH3h0EzYcGkIUV9SREBXVN2+UGgmywjoKYlSofMkVuKxpwVrN7nQQUxxmjSDhFgHvjTBY0ZP", + "CCaNyJ8Uhn57WZIXpoMiN610f/kXVwU/NXKtYbySs8UCuNb9U37BJKe8iMCeEg5zdDcTJnjbXj71gGof", + "m+hArFOYcxDL1kMOzRXlUb24bi6tqbIvgHyBTBKKbmyl25pbe+r1nyvFp/yps9s2cGaw587Q1E0YUNhY", + "DeZWzKv/LgwGN6P/i35rR/fvOX6sNhU1IrAb62/x+2WJ2xgxAzQ0Lucy5R5En5FztQ2dA6iwM0kB0dzY", + "J+dzCNSUwBAoV3RF/t//+b9qciZIsITgCzrt2wh/M3hBklbkYVKUtKSzCFitl6SBHIa9bUmRCzsfSheF", + "P0797GbkaO5mOyjOxghl6hmE93KkvXyRaSvCR79jXRvBvj1pUUVCS6EFg3dqaxTg1wm5UaZTI6UJzLw6", + "41bMtj4Eo9JcGRZm2XvYxd/NndJeseTJJ9L3eCzcJhTeY/b2pV08oWXSxba+/vzVOF7HRkD8yuSyNbF1", + "wTO65KJhbjucBbYynONYXYyPekxDUv4kXcZlvZEkW938Wqy0ONygTJYEUa5fe0Bj3RrNBhTztyzMU0J9", + "3BJRvtiOV1t2lL3L3ZejxFHbcaBjUddil1ilZkY+Km5XrHobXRlN6hjNa66oKJ+CXZsxV5Z6TilGfMUE", + "qHsmEuSCBl8sG+L0qlzP0aEZkV4Er3ZfF0PMenHQgcTUHJ8PFc+Bxh51NVbu9IinJrO+5c9qn95CE+LQ", + "Xs9d/gyIEMbNyeysNmTvvVFNWO9fTVkRst8NyzdCwwvK1JA0ZG6A5e76s4HsczmI1riQP3w1B4M93ooi", + "d5SkL0gTc4GfuXdJM3VdGVBYdnFk1hq5D7BJu+kUTr2Sj7ekm/YdxMK4WhQZZKt+Nnr2GT09eOA5I4t5", + "yAUaPAti49rfB+G3qqHdeC4wTKyXOIfxFzNaD3PxUZtevd6hiTPwRylYEukJHtNNb1F7ztSZ63UG0Tqf", + "yjtUcULVWQ6jxREl+zD3QVlY0TyYFPnrfJVdMRoXZc22Pi5rj8Ap87ZuSEPPTVrq0CrQK32pH+g6XT/H", + "+NrBBVTIs4xeJaOBhUhxu5t3jdiElhfHe/exUSzzRV0+N+7PuKHiWzjuLZGhj0GfBGsfDoLo9mh3S6KV", + "605wsfJIl45oK9S5rMsJ6ifT4Ry0VkiCjx60CW09RNJd1/QKdQMQLF8bFGdgDtNlGO42XBKv00rlfCps", + "u0qP0+ICsdhbZYru5YP3R7vn5eZQ765wouugzG7c/SPnv3112SEPjXu9VYxGcY0rZfM3wJwlTCzH7cr2", + "GbytdVi9uI3QMJgVlZu6PR8qWU+RfqmVr3h4U4MSfmQRfMyilHpoIuMgvFl+XGYwZxEyAqv3Np2s8Tkw", + "XoxN+s+5x6f1I4+ceE4cu7QA5bhOVJv3wsmuvbFhv5ZxDfJv6hWG1gXGdazruN5bBHiA73y5gFFiCS8K", + "IvcusFJB+baEtombwkNX/giGyhqP04W4VRTDfaJCWwRDZQetRptbp/BYJ0o8Db4AV1TvsRMX3xylUPv0", + "69wGyMAOYo8+AJPkaIMlhmFTnXIIriHIdSX8ilxU5m9pZRaocPLOhVqRO5rljvXPzvm0IdIvu48DldY5", + "fxdaY9MlDIKfBkQr6F53gm6AWqgOzBk5LLpN0RNX+1Vq43ItXNUtfbF52A8vCjwjBzQx5jIgFA15qI0O", + "0ihNiICMYpLLwlEwXm3Zvp8m6qVS+Wnv8hX6Ch7NcSQm7NDhVPvlaJO9tHW8hfXlx3ldA5ylT7oQBFnw", + "IAB31zPWngLWQFeQviX44Xf3PeB2HY2Rcfnof54WxaO6csO4UuXVMo2s4FwKgDgQ8kSeJ4TDgvIwAlHg", + "fbuwObeVXz28UP1sC1dSgW6ponnJtDPZua+qbBfeN8vQmlFcBXHd9GJWcYt1fnvXm5CQ9UlY1oEV23bN", + "16CpIZLqmYTMK3l5zN1N2bYn82FjadY5B//W3jlXlJmkfDZZYHspOruEY1jQYNVjhXi2Ody5TPJsMfhG", + "LQbP+vpnff16+nr3LWCeAVaf0Poc2LCd+P556RiD2yO1o3VI8XVn31sJ8ZtUhBWE0DQu4XZd3EU5yCPO", + "QOaXs6w7dzMqjffqzPb5Io8VLy6dtdXsYwCJ/vJ/o8LjdKt+tRDEZkUyBGem5htg/BNHDXUnb5vumv/t", + "q/aV4HfP9Jwubq8oV+ifBowWPv7oWSfpYpABbbDAZN7iltaGe1PRhd8vTY1o/OR6wIZbcW6YmjudhqWO", + "Fm1Vtm6KUd14ltSmvX5oNwiPZ3uV5/zK5LIscPTwF2VHnSVTYMmjuB712tS2a1/5pY28LB5SLH92ynkW", + "8gf5evjElTZJvl961xxHs8o1qm7CVTWqbHTpzTuou9mSs9V76ocjw42KoabtNTr1FvaTcO3a2e1bkS1J", + "lHMlaGAKZaMvVQeAIfYUa1hoUUSP25V28B5BZRf/2Re/jWV4mVydqQtAg8nJGq22h7IYUA78R0s3msP8", + "bkuMV9f7pz+VpqDZn/5EKnmmq6mGFPMqL82yUMoFfEo4xOmlFocpmecYhs0hAioA45WKmInakC/+++9b", + "+ydHWz/D6r9ffkqsBQpTb+AthywQ91NCciklKlf3w5gl+xjrYrfO1J505hAL0L3J37ew5dZ5tci6iZKx", + "AyEZdw+jmmzhUTWHGLQMvVNv/1wuT7TFiL/B/Q47zX4gOQPfZosK/ZjxkZFMqit58nb3jTpYpzTb3mRn", + "9mq2g9leM0hoxiZ7k9ezndmOiT5EnN3Wm9jCTWgprcjV1C2UHehiiTrk1a2d75o0/6JNIUmBdC/e7r75", + "ff/k6Pef3/7XS9fMqfgNIuJRONmbnKRCOuQkJppSQcg3abgy4SvSeD5hsh2Nxdv/MO4vWi7rrSwDV26q", + "91oUpFGuciN/I3R2d17d2ewH5s6rr6Aj3bwNEC/VOBHi7Hd6Wb7ZiuVvq0aq7audAW1fIeZ8vzOgrWrk", + "skRUZrdR0W+fbz5PJyKPY8pXDho5u7Tai73fJlXs/KxmqWLs9ldagu/o8EZjbgQ+C90h/q6wsTZbFfd0", + "Mxf79t0pkHo4jUFiZqYWvX3ZZLuyQNTf1zDqu54aA3o/tzx0PUtf2+82c+j2JEYcuroXtpU8Lra/akv5", + "zTbN2NYXWNXYVpNJCUJ1OKkboqrT+dCImCKa5CrlXzAxews7sjeTOMfp9TUjmujgichDrEE2j2HfBZMv", + "okWrTGfqMJC+cKbP98YZHWH6YRhjfQEewFZiYL0kMoTX7eyMJ6fXQ9q+3iDp1SWwFkZboQMqCBKWS3z4", + "9wCi2/6q5ZFBTPfWFGiYsp8G981CHpoWp/13gV3psGugguPt18B94fijwFtzV9wab7WyYDugSaAT47fc", + "GPhdZ/ViyVbGU50vTb2xMpOZsKYr1EneMN2j1hf1Xx5aKarnehQ3SAUTd+6Mk+O+cbN6r6cg8kj62PmZ", + "g9VEH1JU1L1/oqir94yo5KAMLdSKQ1G3CILfxnxurbj7M4sM5jbD89dA0iIe/GebRe5bxlKzW7XXgViq", + "DsMthvdEsVTt2IM03WjqSN4LaEtKqQZ1+bZooN1PIEs5+lbnO9BKU0qUDWNW92EXBXuamxp57PfxvGpT", + "Lv32WQklHhRoaNrqWIEHWD+8AhXs8X++mXa+vIx6yB3Hz3ocJHh+zNyBkmfDSNb30vDjjstJRj0naq+J", + "ttdC3+vgQYT1p6OzuXum4pHlW7kKlYHH90fb+frO/0R1vuPjv3u21LBZDuJMOz2YZ6ynz5hXwTyDN8N4", + "ki6Y3yrb/A0/62AMn0Sjv08G8QwlmRvtBxPEVuofdVw37j4ra8PNJGkIA+Q03cyzm/fmQ6f07ytOnAuJ", + "Bi18D/wzB8ygZR4E5quO5t3YG2CQjKj2ezvpUINycxd2n4CPZ2zP1yfN47ftr+o/5vr14spPoIfBQpWt", + "qPIeRxnNbfXkE0XfTxG1+jDKFD8fjEdYFcBg4hN8U7530KSOca0vhiVNFkBEEWBFNQR874W7wLT7spyk", + "IeggMb0hdeY3Q+8CJC8DAfT/wyGexGXeqwzTx+vssIUbVVIbF4yo2xmhuMZqJVtAaJ+Xn96ek+3L3XLs", + "dteDn6CSCLHz2isKRiIP0m7oMjUFod002FBWTskF8L/Si+BTvrOz+2eaZX/NeBpigDMWaUH9dhKSS11N", + "J86FJBdAPp4eE0iC1BSU8HG+omK7y/ge5A49xsINBoy3u0wbB3qfJo9boH8V86cPoKxpQqqkr2o67x6N", + "jU3dXiR6dxxLm6zYJZZ7Ut4UiLRZzU1lWo/cbsDk+GlvzDb3VBHV4JhTWNeDoJVLYDsuU+C3v2BMIyea", + "zaWCdgZv8+v38PmDNI7plslbASEW13OyE5OjQ4xbX0BlJZPpBK6zSMkwNtbIx7bNIL+zUHRaSNr94GN6", + "faQ/vtrZqTHb6SRP2D9zMA2QZu5V2vXWL7gdy7elR8xYz2Tl5f8WGeMCp/uJ66v5Z4/WVZuIHLL1qVuL", + "gz+zY44WzYvVDFW51tiwtcg9fmn5QdEFj7ObB0/bVQClgHCxIviYbuev94QKd86t1nmei1K8fUawKoKd", + "jbjfHRa0bQqptXs4nOIBiAIDQ12BQeeGcpgBE0QXRapkiNIVq8IZOT8/Vk0w6guuJSTmVdUh3RaYfGDW", + "eFuEvntJ2axslLS88xDSsk1EaitY3UwfSm43GPGt+dQ9rIxvqiGKNZmAzck5UPWjriR7pKqrVvhUlD2N", + "4YdpfwqSP9ZpRtem96k3wxYWkqrXAsHVySWVTt6B4tZhCYlZFLGysqL3PaEG9yu/baRpd821hpZLl290", + "CsF0rbJlVRGLWXVVRR6GVzvq1TKuMNwGZAI89XUkAp2295kztIkFNq3xOLbQpwdw+UD5BBpA3a06gFsQ", + "eFHCRxN3mQWFcmlJHd3fL2k0dYorTms1VsuN3BOl+4YFLK3kEuqArUESrrexcUveiPq6VhtxXfW1yxI2", + "oLz4I3GQNXUc26bQuk7oIlvdi5o12d0SyLSuoXKKgbt1UU2ldVMSXGfWgTIZbWXgGfkQM4kywZxBFJIg", + "AsoFYdLzLsk9XMzUNX+Ez5Jq4XUNYVvcfcgTxaP3qdCbdXhyyc17ePdJUN/t/DCk7Q9PmfgMcdRgPJoG", + "dTH61ic+lqKvlTwe8i7Hfo8X/SsV9se4BLjPVa3TqLxW0VZsKsub1+wznt8OzzUGrvt85brOPoguLRY2", + "qXAsrYbCEu5SmAqnKYnYJQzE/9Ni3kdLA2aJY6mgJl3pbW5IB/u4MNMizvq4KWyuooFZPk6xxxrsWHd8", + "hLioFxY+fp+Cdt3kM2tfn4AQndelH1s3vTfjBKEkAy6YkFgs2ZaSLzxqzJj/LorHgpBYOMNW2hf2Trc+", + "ZdrDBKOpSru4DkrGecgFrNJEc8WUswXDwPpimojNQV0qQ60cxToe721il+hcJxulZFtDP5mnXp2APfTN", + "56b4A9gYjB+RgfFoQlakkOYdhsYzkNr1TTcsNXj2xV/h1iyKCFxnjAO5thovx32OlYnyDD3MyAGNIrQA", + "LJkgMchlGpI4jyTLIjCJI9NL4FecSaNWOD8/nhKggS7qTnKhu5f6hlIpR0WpblStspSp7ymJgYrclO6z", + "WwutCmIgazg3sHu0jMEs8FZipjDnb4/YIswzLTZ1co7iXRa40UqP7hXaHZJUNHWy8npSF/wEsuO26vC4", + "q4WIVFfxo/bhLtdwsSIizXkAjtefPwdM/0CYcTTl5OhwSlJsSBXSSbrY+mdOI52nwSmOZTt/mkxtaSwa", + "b1c+FFWz7E+malaL6cAkuGz3E+/PZZNRJWSoxR+jbW9Ul/dwLU3Ku82o9Cu39boa/RJ5/1COhQ51FWRd", + "/KbJGjOWDMsG4TXLnZsPm8kBcfvsD5vEgHpa2T40aEnkUIm3UU2dkytzzQwxs7re1l1s2ckls66R1WSO", + "ebawflsWVoUUd2FexXjujdhWh+f7e6q8/tyF5QhWsR3T6052gdhofIh8rMNWbtGBGxa3hzGUd/T6mac8", + "ep4y9QROchZgKR31L7iECpZg7KMJoWmJdORYSKA9WsYWYgzSxLyPf3dDgmzQDR7G75xK8NRjvFcvs3f0", + "2uWCz1zvYbiedW6UA7ifjoJcKy657Oxla+XHAc/XIpteG7EPrg+8obveRI/eUui2MHrIp9dYUfze8rKV", + "CGMxtfytGuI7Iot/Z5yvi6T3obzz1VsfptPfvfM1mPK7LUY6XTUNAxmCADJpPS4eXUziHWKrP8FbWW/I", + "g4QVnrltSv5sf8V/tCeZOcAS12zuFCJicmlkQ10qCq6ZkN2M1FQMw/+0MNVqhlJqWrYLFC3Vvm3HTYoO", + "dnLcXlEero+dqltE53+1ZR5XZvWjsffRJT9/WGMUoqxsYEQvTZQlSTuTHZZmIbqwZqVWcUL3KQjhnC7u", + "i2FXZ1ITjeLa37UUY/1G05w/KIYWeRcNikpaCcAoasX6baH7WLJTdXohXqo3G21WyO2QF+4RBfXK1kbB", + "V3e8EAjdpXjFB7ooqw0/Y/jdYbjF0W4Mr7Lfr2VxwqE5Z1vk4zrTrRQ9HKmbKroOT4FQqdl4F5lnv7E3", + "U439tT6aum1XtRT+LajgSqF3hAdr2lXXsOBuQmh1agKPVHq57wEmxZq1IZ60KbZ673Y8/8vsyd3v/yIv", + "btvLXw10L4zt/jQI1UreaydVbpTtbU2s/ASSET08E25i2u00V6dgqtcnA/VWTwN5n67660mrtCw2jdJp", + "uQKkrS/1Ff9rJMqhuIz5IPCWG/KeKibV1+gbPeE9ixhmWz4ZYdfPPDVAl1RoA+o3gSbmoOqv31uiSjPl", + "ScOEXpnRJlAYInlWcGStRCZr4slmk54EORdoLn5KWU98SU/0rObzOn4AIeMQ4B6mA9m+worDolfrwBFc", + "YrG6wYMeYwcPaM+0H+2Q05/zNG7zo8BRRu1ST7wh7TzSnJp1sIbe/95xSP5xKn5amO0DOzP5GOZt+bTJ", + "Wz6CU7eVSujj1Gc2Q/rD8OqjJIRrS6BFGEkBy1ZyLbJcOHKUl5ekC/FhPhfQwhxH54P6Ztj32lx2Yyyt", + "NeCtl5U98691+VejYMJADjZnkfppScWyu64LTUieRSkNScSSL1bJSTlRIxCFRJQlDg+gK9DfhsqhP6q2", + "f6NieVue5jHeL/WwQ233ahWWt9kt9JvvX90PNSm4fETIt7233XO5WgLHxBDmR6Quc0rfgAnpCVAi0oOB", + "/sfT4/EkaY3+PYEyaOpfx9RgzK13aXa6R6fAc7q4bQSAa/F7+mX8H5GNod2G6iZXHRDH2VWK4Zfdb7na", + "Tms0aLHQixVJEwwGjVOuKyMhJAZVjpCaLayX3vBMep1ypxMhV5H6QUm3T8n6+Fya6JGXplB0ebn7siM6", + "vDNr86DUr22qUYfPPNG8zk9Sxdn3SN4Zu+ZCUTkEsi1LvhMVaQ2UehOoHMEnvIltynkyI6o3uYAovdLp", + "PnQDyoHAdRDlYTts70zlekAFbAlIBJPsEojIL/QdRWIqgyVJE1x5DELQhX7vKZbbcu0A5cGysqyYXh9D", + "slAMYPf7P2/WG9pJ1/3L7nq61ufE3cMTdw9g4f5IqfFxUb/sPlhk1Dcm9dx1DNZ0sgQa4lF8nfx9S0Fg", + "S4OgGdCB1xYK6iBNHGkC15JkitOkc8xzF0kxJWyuZWCM7+gUq2++xaDHxxZbVif0+w4wq1L7pnxsftl9", + "7F42BhLP1SWHZoUrFBgdCFy/qRrO4I4H5Wh/SQeTv22PyXtZRLsA9+ySea8umWvTS4/v21hPNy/5PJyv", + "2z3fQwiRUbfQ43K1e9iHiccfrxeNX1cfKD15fPvlltcPIre8fii5xSzAMmq7kMclwvwRUpc0xZ3XnXif", + "RnkMA5P0Edva9ywvPt3/w1XPNfrZGqGWv7mbP1asUOUYLVrYX9rjfWusrxjFz/0cZLiX4v8WAzYb0Ktn", + "3U9Co9jpwTeb47sJs+dnWjvfcjGriZ4Oz9r+qv8xPFC3HWl1I4O2v5hhRwuUdj0Do3QryGIjdGkTUZ7V", + "zt643W5EmXZ54xZdW11x7xMVdh6KIdm0es9YNizvZRcfwuXyS4sOOY8me5OllJnY296mGZvB7sWMZhki", + "gOn/tTRJlHnLvtYSkFd/xFRz7t94nFtSnXe1Yca2vsCq8pvxfir+LgSezzf/PwAA//8=", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/packages/api/internal/handlers/snapshot_template_create.go b/packages/api/internal/handlers/snapshot_template_create.go index 7908b5c9c0..87915ce573 100644 --- a/packages/api/internal/handlers/snapshot_template_create.go +++ b/packages/api/internal/handlers/snapshot_template_create.go @@ -55,9 +55,12 @@ func (a *APIStore) PostSandboxesSandboxIDSnapshots(c *gin.Context, sandboxID api return } - // Build opts from the optional name + // Build opts from the optional name. memory defaults to true (full memory + // snapshot); memory:false requests a filesystem-only snapshot template, + // derived from the memory checkpoint the source sandbox resumes from. opts := orchestrator.SnapshotTemplateOpts{ - Tag: id.DefaultTag, + Tag: id.DefaultTag, + FilesystemOnly: body.Memory != nil && !*body.Memory, } if body.Name != nil { diff --git a/packages/api/internal/orchestrator/snapshot_template.go b/packages/api/internal/orchestrator/snapshot_template.go index 7c648e2eb4..f1d0f725e9 100644 --- a/packages/api/internal/orchestrator/snapshot_template.go +++ b/packages/api/internal/orchestrator/snapshot_template.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" + "github.com/e2b-dev/infra/packages/api/internal/orchestrator/nodemanager" "github.com/e2b-dev/infra/packages/api/internal/sandbox" "github.com/e2b-dev/infra/packages/db/pkg/types" "github.com/e2b-dev/infra/packages/db/queries" @@ -32,6 +33,11 @@ type SnapshotTemplateOpts struct { Namespace *string // Tag is the build tag parsed from the name, defaults to "default". Tag string + // FilesystemOnly makes the snapshot template persist only the filesystem: + // its build is derived from the memory checkpoint (sharing its rootfs data) + // and sandboxes created from it cold-boot. The source sandbox still gets a + // full memory checkpoint and resumes with its memory intact. + FilesystemOnly bool } // CreateSnapshotTemplate creates a persistent snapshot template from a running sandbox and immediately resumes it. @@ -69,16 +75,33 @@ func (o *Orchestrator) CreateSnapshotTemplate(ctx context.Context, teamID uuid.U return SnapshotTemplateResult{}, fmt.Errorf("node '%s' not found", sbx.NodeID) } - // Snapshot templates are always memory snapshots; filesystem-only checkpoint - // (resume-in-place would need a reboot) is not supported yet. + // The sandbox's own snapshot row is always a full memory snapshot, even for + // a filesystem-only template: the sandbox is memory-resumed from it and it + // can be memory-resumed again later (auto-resume included). upsertResult, err := o.throttledUpsertSnapshot(ctx, buildUpsertSnapshotParams(sbx, node, false)) if err != nil { return SnapshotTemplateResult{}, fmt.Errorf("error upserting snapshot: %w", err) } + buildIDs := []uuid.UUID{upsertResult.BuildID} - snapshotTemplateEnvID, err := o.resolveOrCreateSnapshotTemplate(ctx, sandboxID, teamID, upsertResult.BuildID, sbx.NodeID, sbx.ClusterID, opts) + // A filesystem-only template gets its own derived build that shares the + // memory checkpoint's rootfs data via header references but carries no + // memory snapshot, so sandboxes created from it cold-boot. Otherwise the + // template reuses the memory build directly. + templateBuildID := upsertResult.BuildID + if opts.FilesystemOnly { + templateBuildID, err = o.sqlcDB.CreateSnapshotTemplateBuild(ctx, buildSnapshotTemplateBuildParams(sbx, node)) + if err != nil { + o.failSnapshotBuilds(ctx, buildIDs, err) + + return SnapshotTemplateResult{}, fmt.Errorf("error creating filesystem-only template build: %w", err) + } + buildIDs = append(buildIDs, templateBuildID) + } + + snapshotTemplateEnvID, err := o.resolveOrCreateSnapshotTemplate(ctx, sandboxID, teamID, templateBuildID, sbx.NodeID, sbx.ClusterID, opts) if err != nil { - o.failSnapshotBuild(ctx, upsertResult.BuildID, err) + o.failSnapshotBuilds(ctx, buildIDs, err) return SnapshotTemplateResult{}, err } @@ -87,14 +110,23 @@ func (o *Orchestrator) CreateSnapshotTemplate(ctx context.Context, teamID uuid.U // orchestrator with the same ExecutionID. On error the orchestrator // kills the sandbox itself; RemoveSandbox is still needed to clean up // API-side state (store, routing, analytics). - client, childCtx := node.GetClient(ctx) - _, err = client.Sandbox.Checkpoint(childCtx, &orchestrator.SandboxCheckpointRequest{ + checkpointReq := &orchestrator.SandboxCheckpointRequest{ SandboxId: sbx.SandboxID, BuildId: upsertResult.BuildID.String(), Metadata: map[string]string{storageopts.ObjectMetadataTemplateID: snapshotTemplateEnvID}, - }) + } + if opts.FilesystemOnly { + // The memory build belongs to the sandbox's own snapshot env (like a + // pause build); the derived build is the one owned by the template env. + checkpointReq.Metadata = map[string]string{storageopts.ObjectMetadataTemplateID: upsertResult.TemplateID} + checkpointReq.FilesystemBuildId = templateBuildID.String() + checkpointReq.FilesystemMetadata = map[string]string{storageopts.ObjectMetadataTemplateID: snapshotTemplateEnvID} + } + + client, childCtx := node.GetClient(ctx) + _, err = client.Sandbox.Checkpoint(childCtx, checkpointReq) if err != nil { - o.failSnapshotBuild(ctx, upsertResult.BuildID, err) + o.failSnapshotBuilds(ctx, buildIDs, err) // Complete the snapshotting transition with error — leaves state as // Snapshotting (no restore to Running) and clears the transition key @@ -109,14 +141,16 @@ func (o *Orchestrator) CreateSnapshotTemplate(ctx context.Context, teamID uuid.U } now := time.Now() - err = o.sqlcDB.UpdateEnvBuildStatus(ctx, queries.UpdateEnvBuildStatusParams{ - Status: types.BuildStatusUploaded, - FinishedAt: &now, - Reason: types.BuildReason{}, - BuildID: upsertResult.BuildID, - }) - if err != nil { - return SnapshotTemplateResult{}, fmt.Errorf("error updating build status: %w", err) + for _, buildID := range buildIDs { + err = o.sqlcDB.UpdateEnvBuildStatus(ctx, queries.UpdateEnvBuildStatusParams{ + Status: types.BuildStatusUploaded, + FinishedAt: &now, + Reason: types.BuildReason{}, + BuildID: buildID, + }) + if err != nil { + return SnapshotTemplateResult{}, fmt.Errorf("error updating build status: %w", err) + } } o.snapshotCache.Invalidate(context.WithoutCancel(ctx), sandboxID) @@ -125,19 +159,39 @@ func (o *Orchestrator) CreateSnapshotTemplate(ctx context.Context, teamID uuid.U return SnapshotTemplateResult{ TemplateID: snapshotTemplateEnvID, - BuildID: upsertResult.BuildID, + BuildID: templateBuildID, }, nil } -func (o *Orchestrator) failSnapshotBuild(ctx context.Context, buildID uuid.UUID, cause error) { - err := o.sqlcDB.UpdateEnvBuildStatus(ctx, queries.UpdateEnvBuildStatusParams{ - Status: types.BuildStatusFailed, - FinishedAt: new(time.Now()), - Reason: types.BuildReason{Message: cause.Error()}, - BuildID: buildID, - }) - if err != nil { - telemetry.ReportError(ctx, "error failing build", err) +// buildSnapshotTemplateBuildParams mirrors buildUpsertSnapshotParams' build +// fields for the derived filesystem-only template build: same shape and CPU +// pinning to the source build, just not tied to the sandbox's snapshot env. +func buildSnapshotTemplateBuildParams(sbx sandbox.Sandbox, node *nodemanager.Node) queries.CreateSnapshotTemplateBuildParams { + return queries.CreateSnapshotTemplateBuildParams{ + Vcpu: sbx.VCpu, + RamMb: sbx.RamMB, + FreeDiskSizeMb: 0, + KernelVersion: sbx.KernelVersion, + FirecrackerVersion: sbx.FirecrackerVersion, + EnvdVersion: &sbx.EnvdVersion, + Status: types.BuildStatusSnapshotting, + OriginNodeID: &node.ID, + TotalDiskSizeMb: &sbx.TotalDiskSizeMB, + SourceBuildID: sbx.BuildID, + } +} + +func (o *Orchestrator) failSnapshotBuilds(ctx context.Context, buildIDs []uuid.UUID, cause error) { + for _, buildID := range buildIDs { + err := o.sqlcDB.UpdateEnvBuildStatus(ctx, queries.UpdateEnvBuildStatusParams{ + Status: types.BuildStatusFailed, + FinishedAt: new(time.Now()), + Reason: types.BuildReason{Message: cause.Error()}, + BuildID: buildID, + }) + if err != nil { + telemetry.ReportError(ctx, "error failing build", err) + } } } diff --git a/packages/db/queries/create_snapshot_template_build.sql.go b/packages/db/queries/create_snapshot_template_build.sql.go new file mode 100644 index 0000000000..cc90a23237 --- /dev/null +++ b/packages/db/queries/create_snapshot_template_build.sql.go @@ -0,0 +1,87 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: create_snapshot_template_build.sql + +package queries + +import ( + "context" + + "github.com/e2b-dev/infra/packages/db/pkg/types" + "github.com/google/uuid" +) + +const createSnapshotTemplateBuild = `-- name: CreateSnapshotTemplateBuild :one +INSERT INTO "public"."env_builds" ( + vcpu, + ram_mb, + free_disk_size_mb, + kernel_version, + firecracker_version, + envd_version, + status, + cluster_node_id, + total_disk_size_mb, + updated_at, + cpu_architecture, + cpu_family, + cpu_model, + cpu_model_name, + cpu_flags +) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + now(), + (SELECT eb.cpu_architecture FROM "public"."env_builds" eb WHERE eb.id = $10), + (SELECT eb.cpu_family FROM "public"."env_builds" eb WHERE eb.id = $10), + (SELECT eb.cpu_model FROM "public"."env_builds" eb WHERE eb.id = $10), + (SELECT eb.cpu_model_name FROM "public"."env_builds" eb WHERE eb.id = $10), + (SELECT eb.cpu_flags FROM "public"."env_builds" eb WHERE eb.id = $10) +) +RETURNING id as build_id +` + +type CreateSnapshotTemplateBuildParams struct { + Vcpu int64 + RamMb int64 + FreeDiskSizeMb int64 + KernelVersion string + FirecrackerVersion string + EnvdVersion *string + Status types.BuildStatus + OriginNodeID *string + TotalDiskSizeMb *int64 + SourceBuildID uuid.UUID +} + +// Creates a standalone env_builds row for a snapshot-template build that is +// derived from (and shares data with) another build of the same checkpoint, +// e.g. the filesystem-only sibling of a memory checkpoint. CPU info is copied +// from the source build so the derived build keeps the snapshot's CPU +// compatibility pinned to the original build, mirroring UpsertSnapshot. +func (q *Queries) CreateSnapshotTemplateBuild(ctx context.Context, arg CreateSnapshotTemplateBuildParams) (uuid.UUID, error) { + row := q.db.QueryRow(ctx, createSnapshotTemplateBuild, + arg.Vcpu, + arg.RamMb, + arg.FreeDiskSizeMb, + arg.KernelVersion, + arg.FirecrackerVersion, + arg.EnvdVersion, + arg.Status, + arg.OriginNodeID, + arg.TotalDiskSizeMb, + arg.SourceBuildID, + ) + var build_id uuid.UUID + err := row.Scan(&build_id) + return build_id, err +} diff --git a/packages/db/queries/snapshots/create_snapshot_template_build.sql b/packages/db/queries/snapshots/create_snapshot_template_build.sql new file mode 100644 index 0000000000..a4bf4905ae --- /dev/null +++ b/packages/db/queries/snapshots/create_snapshot_template_build.sql @@ -0,0 +1,41 @@ +-- name: CreateSnapshotTemplateBuild :one +-- Creates a standalone env_builds row for a snapshot-template build that is +-- derived from (and shares data with) another build of the same checkpoint, +-- e.g. the filesystem-only sibling of a memory checkpoint. CPU info is copied +-- from the source build so the derived build keeps the snapshot's CPU +-- compatibility pinned to the original build, mirroring UpsertSnapshot. +INSERT INTO "public"."env_builds" ( + vcpu, + ram_mb, + free_disk_size_mb, + kernel_version, + firecracker_version, + envd_version, + status, + cluster_node_id, + total_disk_size_mb, + updated_at, + cpu_architecture, + cpu_family, + cpu_model, + cpu_model_name, + cpu_flags +) +VALUES ( + @vcpu, + @ram_mb, + @free_disk_size_mb, + @kernel_version, + @firecracker_version, + @envd_version, + @status, + @origin_node_id, + @total_disk_size_mb, + now(), + (SELECT eb.cpu_architecture FROM "public"."env_builds" eb WHERE eb.id = @source_build_id), + (SELECT eb.cpu_family FROM "public"."env_builds" eb WHERE eb.id = @source_build_id), + (SELECT eb.cpu_model FROM "public"."env_builds" eb WHERE eb.id = @source_build_id), + (SELECT eb.cpu_model_name FROM "public"."env_builds" eb WHERE eb.id = @source_build_id), + (SELECT eb.cpu_flags FROM "public"."env_builds" eb WHERE eb.id = @source_build_id) +) +RETURNING id as build_id; diff --git a/packages/orchestrator/cmd/create-build/proxyport_test.go b/packages/orchestrator/cmd/create-build/proxyport_test.go index 5f9452664a..eb897a4994 100644 --- a/packages/orchestrator/cmd/create-build/proxyport_test.go +++ b/packages/orchestrator/cmd/create-build/proxyport_test.go @@ -1,3 +1,5 @@ +//go:build linux + package main import "testing" diff --git a/packages/orchestrator/cmd/inspect-build/inspect.go b/packages/orchestrator/cmd/inspect-build/inspect.go index 10f3608cee..bfabbc9202 100644 --- a/packages/orchestrator/cmd/inspect-build/inspect.go +++ b/packages/orchestrator/cmd/inspect-build/inspect.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/inspect-build/main.go b/packages/orchestrator/cmd/inspect-build/main.go index d65c0a061e..78eec2bda6 100644 --- a/packages/orchestrator/cmd/inspect-build/main.go +++ b/packages/orchestrator/cmd/inspect-build/main.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/inspect-build/render_human.go b/packages/orchestrator/cmd/inspect-build/render_human.go index 2c29d9d5ff..ea5769eba1 100644 --- a/packages/orchestrator/cmd/inspect-build/render_human.go +++ b/packages/orchestrator/cmd/inspect-build/render_human.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/inspect-build/render_json.go b/packages/orchestrator/cmd/inspect-build/render_json.go index b696228b26..1ceabf4e90 100644 --- a/packages/orchestrator/cmd/inspect-build/render_json.go +++ b/packages/orchestrator/cmd/inspect-build/render_json.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/inspect-build/report.go b/packages/orchestrator/cmd/inspect-build/report.go index 4b8fd34307..1f276740fb 100644 --- a/packages/orchestrator/cmd/inspect-build/report.go +++ b/packages/orchestrator/cmd/inspect-build/report.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/inspect-build/report_test.go b/packages/orchestrator/cmd/inspect-build/report_test.go index 40ddb56be7..7a2677aa97 100644 --- a/packages/orchestrator/cmd/inspect-build/report_test.go +++ b/packages/orchestrator/cmd/inspect-build/report_test.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/inspect-build/validate.go b/packages/orchestrator/cmd/inspect-build/validate.go index 0772ac45fb..ee286a8345 100644 --- a/packages/orchestrator/cmd/inspect-build/validate.go +++ b/packages/orchestrator/cmd/inspect-build/validate.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/inspect-build/validate_test.go b/packages/orchestrator/cmd/inspect-build/validate_test.go index 23f2fd2cb7..640f64c94e 100644 --- a/packages/orchestrator/cmd/inspect-build/validate_test.go +++ b/packages/orchestrator/cmd/inspect-build/validate_test.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/resume-build/gdb.go b/packages/orchestrator/cmd/resume-build/gdb.go index f6bcca1f24..35d1009c89 100644 --- a/packages/orchestrator/cmd/resume-build/gdb.go +++ b/packages/orchestrator/cmd/resume-build/gdb.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/cmd/resume-build/gdb_test.go b/packages/orchestrator/cmd/resume-build/gdb_test.go index d39735d97b..30d65cbe3d 100644 --- a/packages/orchestrator/cmd/resume-build/gdb_test.go +++ b/packages/orchestrator/cmd/resume-build/gdb_test.go @@ -1,3 +1,5 @@ +//go:build linux + package main import ( diff --git a/packages/orchestrator/orchestrator.proto b/packages/orchestrator/orchestrator.proto index 64935b1d40..d7cb95be02 100644 --- a/packages/orchestrator/orchestrator.proto +++ b/packages/orchestrator/orchestrator.proto @@ -179,6 +179,17 @@ message SandboxCheckpointRequest { // index (e.g. template_id). Opaque to the orchestrator, which just forwards // it to object metadata. map metadata = 4; + + // When set, the checkpoint additionally derives a filesystem-only sibling + // build under this ID: it shares the checkpoint's rootfs data via header + // references but carries no memory snapshot, so sandboxes created from it + // cold-boot (reboot) from the rootfs. The main build_id snapshot stays a + // full memory snapshot that the source sandbox is resumed from, exactly + // like a plain checkpoint. Unset = no derived build (existing behavior). + string filesystem_build_id = 5; + + // Provenance for the sibling build's storage objects, analogous to metadata. + map filesystem_metadata = 6; } message SandboxCheckpointResponse { diff --git a/packages/orchestrator/pkg/sandbox/sandbox.go b/packages/orchestrator/pkg/sandbox/sandbox.go index 07a78a9f52..d07d0a8cc5 100644 --- a/packages/orchestrator/pkg/sandbox/sandbox.go +++ b/packages/orchestrator/pkg/sandbox/sandbox.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "maps" "net/http" "sync" "time" @@ -1222,6 +1223,7 @@ func (s *Sandbox) Shutdown(ctx context.Context) error { type pauseOptions struct { filesystemSnapshot bool + filesystemSibling uuid.UUID } type PauseOption func(*pauseOptions) @@ -1234,6 +1236,15 @@ func WithFilesystemSnapshot() PauseOption { return func(o *pauseOptions) { o.filesystemSnapshot = true } } +// WithFilesystemSibling makes a memory pause additionally derive a +// filesystem-only sibling snapshot under the given build ID (see +// Snapshot.FilesystemSibling): a separate build that shares this pause's +// rootfs data through header references and carries no memory snapshot, so it +// is resumed by cold boot. Mutually exclusive with WithFilesystemSnapshot. +func WithFilesystemSibling(buildID uuid.UUID) PauseOption { + return func(o *pauseOptions) { o.filesystemSibling = buildID } +} + // Pause creates a snapshot of the sandbox. // // Currently the memory snapshotting works like this: @@ -1260,9 +1271,13 @@ func (s *Sandbox) Pause( for _, opt := range opts { opt(&pauseOpts) } + if pauseOpts.filesystemSnapshot && pauseOpts.filesystemSibling != uuid.Nil { + return nil, errors.New("filesystem-only snapshot cannot also derive a filesystem sibling") + } ctx, span := tracer.Start(ctx, "sandbox-snapshot", trace.WithAttributes( attribute.Bool("fs-only-snapshot", pauseOpts.filesystemSnapshot), + attribute.Bool("fs-sibling-snapshot", pauseOpts.filesystemSibling != uuid.Nil), )) defer span.End() @@ -1314,6 +1329,15 @@ func (s *Sandbox) Pause( // Memory prefetch refers to the memfile, which is not persisted. m.Prefetch = nil + } else if pauseOpts.filesystemSibling != uuid.Nil { + // The sibling is cold-booted from the rootfs alone, so the guest page + // cache must be flushed before pause or the sibling would miss + // acknowledged writes. Sync instead of fsfreeze: this memory snapshot + // IS resumed, and RAM captured with a frozen filesystem would resume + // frozen, wedging every write until an explicit thaw. + if err := s.guestSync(ctx, s.guestSyncTimeout(ctx)); err != nil { + return nil, fmt.Errorf("guest sync before filesystem sibling snapshot: %w", err) + } } // Record the snapshot kind in metadata so the resume path picks reboot vs @@ -1414,7 +1438,7 @@ func (s *Sandbox) Pause( return nil, err } - return &Snapshot{ + snapshot := &Snapshot{ Snapfile: snapfile, Metafile: metadataFileLink, MemorySnapshot: mem, @@ -1426,6 +1450,73 @@ func (s *Sandbox) Pause( BuildID: buildID, + cleanup: cleanup, + } + + if pauseOpts.filesystemSibling != uuid.Nil { + sibling, err := s.deriveFilesystemSibling(ctx, cleanup, m, rootfsHeader, pauseOpts.filesystemSibling) + if err != nil { + return nil, fmt.Errorf("derive filesystem sibling snapshot: %w", err) + } + snapshot.FilesystemSibling = sibling + } + + return snapshot, nil +} + +// deriveFilesystemSibling builds the filesystem-only sibling of a memory +// pause (see WithFilesystemSibling): a snapshot under its own build ID whose +// rootfs header maps every block to the memory build's data (next generation, +// no data of its own) and whose memory snapshot is empty, so it is resumed by +// cold boot. Its upload therefore waits on the memory build's upload future. +func (s *Sandbox) deriveFilesystemSibling( + ctx context.Context, + cleanup *Cleanup, + m metadata.Template, + rootfsHeader *header.Header, + buildID uuid.UUID, +) (*Snapshot, error) { + cachePaths, err := storage.Paths{BuildID: buildID.String()}.Cache(s.config.StorageConfig) + if err != nil { + return nil, fmt.Errorf("create sibling cache paths: %w", err) + } + cleanup.AddNoContext(ctx, cachePaths.Close) + + siblingHeader := &header.Header{ + Metadata: rootfsHeader.Metadata.NextGeneration(buildID), + Builds: maps.Clone(rootfsHeader.Builds), + Mapping: rootfsHeader.Mapping, + IncompletePendingUpload: true, + } + + // SameVersionTemplate drops the memory build's prefetch (refers to its + // memfile) and filesystem-only mark, which is then set for the sibling. + siblingTemplate := m.Template + siblingTemplate.BuildID = buildID.String() + siblingMeta := m.SameVersionTemplate(siblingTemplate).MarkFilesystemOnly(true) + + metafile := template.NewLocalFileLink(cachePaths.CacheMetadata()) + cleanup.AddNoContext(ctx, metafile.Close) + if err := siblingMeta.ToFile(metafile.Path()); err != nil { + return nil, fmt.Errorf("write sibling metadata: %w", err) + } + + return &Snapshot{ + // No snapfile: a filesystem-only build is resumed by reboot, and unlike a + // filesystem-only pause there is no FC snapfile of its own to cache. + Snapfile: nil, + Metafile: metafile, + MemorySnapshot: MemorySnapshot{Diff: &build.NoDiff{}, DiffHeader: NewResolvedDiffHeader(nil)}, + RootfsDiff: &build.NoDiff{}, + RootfsDiffHeader: NewResolvedDiffHeader(siblingHeader), + SchedulingMetadata: scheduling.FromHeaders(buildID, nil, siblingHeader, 0), + FilesystemSnapshot: true, + RootfsBlockSize: rootfsHeader.Metadata.BlockSize, + + BuildID: buildID, + + // The parent snapshot's cleanup owns the sibling's files: a failed pause + // runs it once for both, and on success ownership moves to the caches. cleanup: cleanup, }, nil } diff --git a/packages/orchestrator/pkg/sandbox/snapshot.go b/packages/orchestrator/pkg/sandbox/snapshot.go index 9b90397960..c2cca9d759 100644 --- a/packages/orchestrator/pkg/sandbox/snapshot.go +++ b/packages/orchestrator/pkg/sandbox/snapshot.go @@ -45,6 +45,12 @@ type Snapshot struct { // produces a NoDiff memfile but still needs its snapfile uploaded. FilesystemSnapshot bool + // FilesystemSibling is the filesystem-only sibling snapshot derived from + // this memory pause when requested via WithFilesystemSibling: a separate + // build sharing this snapshot's rootfs data via header references. Its + // upload must run after this snapshot's upload has finished. + FilesystemSibling *Snapshot + // RootfsBlockSize is captured sync at Pause time — needed sync by NewUpload's // compression validation. (The memfile block size lives in // MemorySnapshot.BlockSize.) diff --git a/packages/orchestrator/pkg/server/prefetch_harvest.go b/packages/orchestrator/pkg/server/prefetch_harvest.go index a739e49da6..e269639f6e 100644 --- a/packages/orchestrator/pkg/server/prefetch_harvest.go +++ b/packages/orchestrator/pkg/server/prefetch_harvest.go @@ -1,3 +1,5 @@ +//go:build linux + package server import ( diff --git a/packages/orchestrator/pkg/server/sandboxes.go b/packages/orchestrator/pkg/server/sandboxes.go index f01d3d0b8b..b4f988c97c 100644 --- a/packages/orchestrator/pkg/server/sandboxes.go +++ b/packages/orchestrator/pkg/server/sandboxes.go @@ -644,7 +644,7 @@ func (s *Server) Pause(ctx context.Context, in *orchestrator.SandboxPauseRequest defer s.stopSandboxAsync(context.WithoutCancel(ctx), sbx) // Fire and forget - upload completes in the background - res, err := s.snapshotAndCacheSandbox(ctx, sbx, in.GetBuildId(), map[string]string{storage.ObjectMetadataTemplateID: in.GetTemplateId()}, storage.ObjectOriginPause, in.GetFilesystemOnly()) + res, err := s.snapshotAndCacheSandbox(ctx, sbx, in.GetBuildId(), map[string]string{storage.ObjectMetadataTemplateID: in.GetTemplateId()}, storage.ObjectOriginPause, in.GetFilesystemOnly(), nil) if err != nil { telemetry.ReportCriticalError(ctx, "error snapshotting sandbox", err, telemetry.WithSandboxID(in.GetSandboxId())) @@ -736,6 +736,17 @@ func (s *Server) Checkpoint(ctx context.Context, in *orchestrator.SandboxCheckpo return nil, status.Errorf(codes.FailedPrecondition, "%s", err.Error()) } + // A filesystem-only snapshot template is a derived sibling of the memory + // checkpoint build (see SandboxCheckpointRequest.filesystem_build_id). + var fsSibling *filesystemSiblingSpec + if fsID := in.GetFilesystemBuildId(); fsID != "" { + parsed, err := uuid.Parse(fsID) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid filesystem build id '%s': %s", fsID, err) + } + fsSibling = &filesystemSiblingSpec{buildID: parsed, provenance: in.GetFilesystemMetadata()} + } + // Acquire the starting semaphore before resuming, same as Create/Pause. if err := s.waitForAcquire(ctx); err != nil { return nil, err @@ -756,9 +767,7 @@ func (s *Server) Checkpoint(ctx context.Context, in *orchestrator.SandboxCheckpo sbxlogger.E(sbx).Info(ctx, "Checkpointing sandbox") - // Checkpoint always takes a full memory snapshot; filesystem-only checkpoint - // (resume-in-place would need to reboot) is not supported yet. - res, err := s.snapshotAndCacheSandbox(ctx, sbx, in.GetBuildId(), in.GetMetadata(), storage.ObjectOriginSnapshotTemplate, false) + res, err := s.snapshotAndCacheSandbox(ctx, sbx, in.GetBuildId(), in.GetMetadata(), storage.ObjectOriginSnapshotTemplate, false, fsSibling) if err != nil { telemetry.ReportCriticalError(ctx, "error snapshotting sandbox for checkpoint", err, telemetry.WithSandboxID(in.GetSandboxId())) @@ -823,10 +832,37 @@ func (s *Server) Checkpoint(ctx context.Context, in *orchestrator.SandboxCheckpo } } - if s.featureFlags.BoolFlag(ctx, featureflags.PeerToPeerAsyncCheckpointFlag) { + switch { + case res.filesystemSibling != nil: + // The sibling's header references the memory build's data and its upload + // waits on that build's upload future, so run the two sequentially and + // synchronously — the sibling upload is header + metadata only, KB-scale. + // On failure tear down the resumed sandbox, same as the plain sync path. + uploadCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), uploadTimeout) + defer cancel() + + err := res.upload.Run(uploadCtx) + res.completeUpload(uploadCtx, err) + if err == nil { + err = res.filesystemSibling.upload.Run(uploadCtx) + res.filesystemSibling.completeUpload(uploadCtx, err) + } else { + // Finish the sibling's upload future so nothing waits on it forever. + res.filesystemSibling.completeUpload(uploadCtx, fmt.Errorf("memory build upload failed: %w", err)) + } + + if err != nil { + telemetry.ReportCriticalError(ctx, "error uploading snapshot for checkpoint", err, telemetry.WithSandboxID(in.GetSandboxId())) + + s.sandboxFactory.Sandboxes.MarkStopping(ctx, resumedSbx.Runtime.SandboxID, resumedSbx.LifecycleID) + s.stopSandboxAsync(context.WithoutCancel(ctx), resumedSbx) + + return nil, status.Errorf(codes.Internal, "error uploading snapshot for checkpoint '%s': %s", in.GetSandboxId(), err) + } + case s.featureFlags.BoolFlag(ctx, featureflags.PeerToPeerAsyncCheckpointFlag): // Async: return immediately; peer nodes can pull chunks from us during the upload window. s.uploadSnapshotAsync(ctx, resumedSbx, res) - } else { + default: // Sync: wait for upload before returning so a failed upload is surfaced to the caller. // On failure, tear down the resumed sandbox — without a persisted snapshot it cannot // be paused or resumed later. @@ -899,6 +935,20 @@ type snapshotResult struct { // with. The prefetch harvest reuses it verbatim when re-uploading the // metadata object, so the two can never drift. objectMetadata storage.ObjectMetadata + + // filesystemSibling is the derived filesystem-only sibling build's result + // (nil unless requested; its meta field is left zero). Its upload waits on + // this build's upload future, so Run it only after this build's upload has + // been completed with completeUpload. + filesystemSibling *snapshotResult +} + +// filesystemSiblingSpec asks snapshotAndCacheSandbox to additionally derive a +// filesystem-only sibling build of the memory snapshot — the snapshot-template +// build of a memory:false snapshot — and prepare its cache entry and upload. +type filesystemSiblingSpec struct { + buildID uuid.UUID + provenance map[string]string } // snapshotAndCacheSandbox creates a snapshot of a sandbox and adds it to the @@ -911,6 +961,7 @@ func (s *Server) snapshotAndCacheSandbox( provenance map[string]string, buildOrigin storage.ObjectOrigin, filesystemOnly bool, + fsSibling *filesystemSiblingSpec, ) (*snapshotResult, error) { meta, err := sbx.Template.Metadata() if err != nil { @@ -927,6 +978,9 @@ func (s *Server) snapshotAndCacheSandbox( if filesystemOnly { pauseOpts = append(pauseOpts, sandbox.WithFilesystemSnapshot()) } + if fsSibling != nil { + pauseOpts = append(pauseOpts, sandbox.WithFilesystemSibling(fsSibling.buildID)) + } snapshot, err := sbx.Pause(ctx, meta, sandbox.SnapshotUseCasePause, pauseOpts...) if err != nil { @@ -992,12 +1046,67 @@ func (s *Server) snapshotAndCacheSandbox( } } - return &snapshotResult{ + res := &snapshotResult{ meta: meta, schedulingMetadata: snapshot.SchedulingMetadata, upload: upload, completeUpload: completeUpload, objectMetadata: objectMetadata, + } + + if sibling := snapshot.FilesystemSibling; sibling != nil { + res.filesystemSibling, err = s.registerFilesystemSibling(ctx, sbx, sibling, fsSibling.provenance, buildOrigin) + if err != nil { + return nil, fmt.Errorf("register filesystem sibling: %w", err) + } + } + + return res, nil +} + +// registerFilesystemSibling adds the derived filesystem-only sibling snapshot +// (see sandbox.WithFilesystemSibling) to the local template cache and registers +// its upload — a header + metadata upload only, since the sibling shares the +// memory build's rootfs data. No peer-to-peer chunk registration: the sibling +// has no data of its own to serve. +func (s *Server) registerFilesystemSibling( + ctx context.Context, + sbx *sandbox.Sandbox, + snap *sandbox.Snapshot, + provenance map[string]string, + buildOrigin storage.ObjectOrigin, +) (*snapshotResult, error) { + err := s.templateCache.AddSnapshot( + ctx, + snap.BuildID.String(), + snap.MemorySnapshot.DiffHeader, + snap.RootfsDiffHeader, + snap.Snapfile, + snap.Metafile, + snap.MemorySnapshot.Diff, + snap.RootfsDiff, + ) + if err != nil { + return nil, fmt.Errorf("error adding sibling snapshot to template cache: %w", err) + } + + objectMetadata := storage.ObjectMetadata{} + maps.Copy(objectMetadata, provenance) + objectMetadata[storage.ObjectMetadataTeamID] = sbx.Runtime.TeamID + objectMetadata[storage.ObjectMetadataBuildOrigin] = string(buildOrigin) + + upload, err := sandbox.NewUpload(ctx, s.uploads, snap, s.persistence, s.config.StorageConfig.CompressConfig, s.featureFlags, storage.UseCasePause, objectMetadata) + if err != nil { + return nil, fmt.Errorf("register sibling upload: %w", err) + } + + return &snapshotResult{ + schedulingMetadata: snap.SchedulingMetadata, + upload: upload, + completeUpload: func(ctx context.Context, uploadErr error) { + upload.Finish(ctx, uploadErr) + }, + objectMetadata: objectMetadata, }, nil } diff --git a/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go b/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go index 06b039d1f2..4d3c2a16a7 100644 --- a/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go +++ b/packages/shared/pkg/grpc/orchestrator/orchestrator.pb.go @@ -1246,6 +1246,15 @@ type SandboxCheckpointRequest struct { // index (e.g. template_id). Opaque to the orchestrator, which just forwards // it to object metadata. Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // When set, the checkpoint additionally derives a filesystem-only sibling + // build under this ID: it shares the checkpoint's rootfs data via header + // references but carries no memory snapshot, so sandboxes created from it + // cold-boot (reboot) from the rootfs. The main build_id snapshot stays a + // full memory snapshot that the source sandbox is resumed from, exactly + // like a plain checkpoint. Unset = no derived build (existing behavior). + FilesystemBuildId string `protobuf:"bytes,5,opt,name=filesystem_build_id,json=filesystemBuildId,proto3" json:"filesystem_build_id,omitempty"` + // Provenance for the sibling build's storage objects, analogous to metadata. + FilesystemMetadata map[string]string `protobuf:"bytes,6,rep,name=filesystem_metadata,json=filesystemMetadata,proto3" json:"filesystem_metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *SandboxCheckpointRequest) Reset() { @@ -1301,6 +1310,20 @@ func (x *SandboxCheckpointRequest) GetMetadata() map[string]string { return nil } +func (x *SandboxCheckpointRequest) GetFilesystemBuildId() string { + if x != nil { + return x.FilesystemBuildId + } + return "" +} + +func (x *SandboxCheckpointRequest) GetFilesystemMetadata() map[string]string { + if x != nil { + return x.FilesystemMetadata + } + return nil +} + type SandboxCheckpointResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1819,7 +1842,7 @@ var file_orchestrator_proto_rawDesc = []byte{ 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x12, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x69, 0x6e, 0x67, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xd6, 0x01, 0x0a, 0x18, 0x53, 0x61, 0x6e, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb1, 0x03, 0x0a, 0x18, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, @@ -1829,77 +1852,91 @@ var file_orchestrator_proto_rawDesc = []byte{ 0x0b, 0x32, 0x27, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x61, 0x0a, 0x19, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, - 0x0a, 0x13, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x53, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x12, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x22, 0xc7, 0x01, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, - 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x44, - 0x0a, 0x13, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x69, - 0x6e, 0x67, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, - 0x6f, 0x78, 0x65, 0x73, 0x22, 0x71, 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x49, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x49, 0x64, 0x12, 0x62, 0x0a, 0x13, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x31, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, + 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x45, 0x0a, 0x17, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x61, 0x0a, 0x19, + 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x13, 0x73, 0x63, 0x68, + 0x65, 0x64, 0x75, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, + 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x12, 0x73, 0x63, 0x68, + 0x65, 0x64, 0x75, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, + 0xc7, 0x01, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x4b, 0x0a, 0x1f, 0x53, 0x61, 0x6e, 0x64, 0x62, - 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x61, 0x63, - 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x73, 0x32, 0xba, 0x03, 0x0a, 0x0e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, - 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x37, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x04, 0x4c, 0x69, 0x73, - 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x53, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x37, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, - 0x65, 0x12, 0x14, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x50, 0x61, 0x75, 0x73, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, - 0x0a, 0x0a, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x53, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, - 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x20, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, - 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, - 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x44, 0x0a, 0x13, 0x53, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2d, 0x0a, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x65, 0x73, 0x22, + 0x71, 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x43, 0x0a, + 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, + 0x6d, 0x65, 0x22, 0x4b, 0x0a, 0x1f, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x32, + 0xba, 0x03, 0x0a, 0x0e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, + 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x14, 0x2e, 0x53, + 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x50, 0x61, 0x75, 0x73, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0a, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, + 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, + 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x53, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x65, 0x32, 0x62, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, + 0x2f, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1914,7 +1951,7 @@ func file_orchestrator_proto_rawDescGZIP() []byte { return file_orchestrator_proto_rawDescData } -var file_orchestrator_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_orchestrator_proto_msgTypes = make([]protoimpl.MessageInfo, 28) var file_orchestrator_proto_goTypes = []interface{}{ (*SandboxConfig)(nil), // 0: SandboxConfig (*SandboxAutoResumeConfig)(nil), // 1: SandboxAutoResumeConfig @@ -1943,8 +1980,9 @@ var file_orchestrator_proto_goTypes = []interface{}{ nil, // 24: SandboxNetworkTransform.HeadersEntry nil, // 25: SandboxNetworkEgressConfig.RulesEntry nil, // 26: SandboxCheckpointRequest.MetadataEntry - (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 28: google.protobuf.Empty + nil, // 27: SandboxCheckpointRequest.FilesystemMetadataEntry + (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 29: google.protobuf.Empty } var file_orchestrator_proto_depIdxs = []int32{ 22, // 0: SandboxConfig.env_vars:type_name -> SandboxConfig.EnvVarsEntry @@ -1959,40 +1997,41 @@ var file_orchestrator_proto_depIdxs = []int32{ 5, // 9: SandboxNetworkDomainRules.rules:type_name -> SandboxNetworkRule 25, // 10: SandboxNetworkEgressConfig.rules:type_name -> SandboxNetworkEgressConfig.RulesEntry 0, // 11: SandboxCreateRequest.sandbox:type_name -> SandboxConfig - 27, // 12: SandboxCreateRequest.start_time:type_name -> google.protobuf.Timestamp - 27, // 13: SandboxCreateRequest.end_time:type_name -> google.protobuf.Timestamp + 28, // 12: SandboxCreateRequest.start_time:type_name -> google.protobuf.Timestamp + 28, // 13: SandboxCreateRequest.end_time:type_name -> google.protobuf.Timestamp 14, // 14: SandboxCreateResponse.scheduling_metadata:type_name -> SchedulingMetadata - 27, // 15: SandboxUpdateRequest.end_time:type_name -> google.protobuf.Timestamp + 28, // 15: SandboxUpdateRequest.end_time:type_name -> google.protobuf.Timestamp 7, // 16: SandboxUpdateRequest.egress:type_name -> SandboxNetworkEgressConfig 14, // 17: SandboxPauseResponse.scheduling_metadata:type_name -> SchedulingMetadata 26, // 18: SandboxCheckpointRequest.metadata:type_name -> SandboxCheckpointRequest.MetadataEntry - 14, // 19: SandboxCheckpointResponse.scheduling_metadata:type_name -> SchedulingMetadata - 0, // 20: RunningSandbox.config:type_name -> SandboxConfig - 27, // 21: RunningSandbox.start_time:type_name -> google.protobuf.Timestamp - 27, // 22: RunningSandbox.end_time:type_name -> google.protobuf.Timestamp - 18, // 23: SandboxListResponse.sandboxes:type_name -> RunningSandbox - 27, // 24: CachedBuildInfo.expiration_time:type_name -> google.protobuf.Timestamp - 20, // 25: SandboxListCachedBuildsResponse.builds:type_name -> CachedBuildInfo - 6, // 26: SandboxNetworkEgressConfig.RulesEntry.value:type_name -> SandboxNetworkDomainRules - 9, // 27: SandboxService.Create:input_type -> SandboxCreateRequest - 11, // 28: SandboxService.Update:input_type -> SandboxUpdateRequest - 28, // 29: SandboxService.List:input_type -> google.protobuf.Empty - 12, // 30: SandboxService.Delete:input_type -> SandboxDeleteRequest - 13, // 31: SandboxService.Pause:input_type -> SandboxPauseRequest - 16, // 32: SandboxService.Checkpoint:input_type -> SandboxCheckpointRequest - 28, // 33: SandboxService.ListCachedBuilds:input_type -> google.protobuf.Empty - 10, // 34: SandboxService.Create:output_type -> SandboxCreateResponse - 28, // 35: SandboxService.Update:output_type -> google.protobuf.Empty - 19, // 36: SandboxService.List:output_type -> SandboxListResponse - 28, // 37: SandboxService.Delete:output_type -> google.protobuf.Empty - 15, // 38: SandboxService.Pause:output_type -> SandboxPauseResponse - 17, // 39: SandboxService.Checkpoint:output_type -> SandboxCheckpointResponse - 21, // 40: SandboxService.ListCachedBuilds:output_type -> SandboxListCachedBuildsResponse - 34, // [34:41] is the sub-list for method output_type - 27, // [27:34] is the sub-list for method input_type - 27, // [27:27] is the sub-list for extension type_name - 27, // [27:27] is the sub-list for extension extendee - 0, // [0:27] is the sub-list for field type_name + 27, // 19: SandboxCheckpointRequest.filesystem_metadata:type_name -> SandboxCheckpointRequest.FilesystemMetadataEntry + 14, // 20: SandboxCheckpointResponse.scheduling_metadata:type_name -> SchedulingMetadata + 0, // 21: RunningSandbox.config:type_name -> SandboxConfig + 28, // 22: RunningSandbox.start_time:type_name -> google.protobuf.Timestamp + 28, // 23: RunningSandbox.end_time:type_name -> google.protobuf.Timestamp + 18, // 24: SandboxListResponse.sandboxes:type_name -> RunningSandbox + 28, // 25: CachedBuildInfo.expiration_time:type_name -> google.protobuf.Timestamp + 20, // 26: SandboxListCachedBuildsResponse.builds:type_name -> CachedBuildInfo + 6, // 27: SandboxNetworkEgressConfig.RulesEntry.value:type_name -> SandboxNetworkDomainRules + 9, // 28: SandboxService.Create:input_type -> SandboxCreateRequest + 11, // 29: SandboxService.Update:input_type -> SandboxUpdateRequest + 29, // 30: SandboxService.List:input_type -> google.protobuf.Empty + 12, // 31: SandboxService.Delete:input_type -> SandboxDeleteRequest + 13, // 32: SandboxService.Pause:input_type -> SandboxPauseRequest + 16, // 33: SandboxService.Checkpoint:input_type -> SandboxCheckpointRequest + 29, // 34: SandboxService.ListCachedBuilds:input_type -> google.protobuf.Empty + 10, // 35: SandboxService.Create:output_type -> SandboxCreateResponse + 29, // 36: SandboxService.Update:output_type -> google.protobuf.Empty + 19, // 37: SandboxService.List:output_type -> SandboxListResponse + 29, // 38: SandboxService.Delete:output_type -> google.protobuf.Empty + 15, // 39: SandboxService.Pause:output_type -> SandboxPauseResponse + 17, // 40: SandboxService.Checkpoint:output_type -> SandboxCheckpointResponse + 21, // 41: SandboxService.ListCachedBuilds:output_type -> SandboxListCachedBuildsResponse + 35, // [35:42] is the sub-list for method output_type + 28, // [28:35] is the sub-list for method input_type + 28, // [28:28] is the sub-list for extension type_name + 28, // [28:28] is the sub-list for extension extendee + 0, // [0:28] is the sub-list for field type_name } func init() { file_orchestrator_proto_init() } @@ -2278,7 +2317,7 @@ func file_orchestrator_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_orchestrator_proto_rawDesc, NumEnums: 0, - NumMessages: 27, + NumMessages: 28, NumExtensions: 0, NumServices: 1, }, diff --git a/spec/openapi.yml b/spec/openapi.yml index 18431862ae..adb16aa8e3 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -807,6 +807,16 @@ components: name: type: string description: Optional name for the snapshot template. If a snapshot template with this name already exists, a new build will be assigned to the existing template instead of creating a new one. + memory: + type: boolean + default: true + description: >- + Whether the snapshot template captures the sandbox's memory. When + false, the template persists only the filesystem (a filesystem-only + snapshot): sandboxes created from it cold-boot (reboot) from disk + instead of restoring in-memory state. The source sandbox itself is + unaffected either way — it is checkpointed with memory and resumes + with its processes and memory intact. Defaults to true. SandboxPauseRequest: type: object diff --git a/tests/integration/internal/api/generated.go b/tests/integration/internal/api/generated.go index 3661a9b6a5..1a703b96cd 100644 --- a/tests/integration/internal/api/generated.go +++ b/tests/integration/internal/api/generated.go @@ -983,6 +983,9 @@ type SandboxRefreshRequest struct { // SandboxSnapshotRequest defines model for SandboxSnapshotRequest. type SandboxSnapshotRequest struct { + // Memory Whether the snapshot template captures the sandbox's memory. When false, the template persists only the filesystem (a filesystem-only snapshot): sandboxes created from it cold-boot (reboot) from disk instead of restoring in-memory state. The source sandbox itself is unaffected either way — it is checkpointed with memory and resumes with its processes and memory intact. Defaults to true. + Memory *bool `json:"memory,omitempty"` + // Name Optional name for the snapshot template. If a snapshot template with this name already exists, a new build will be assigned to the existing template instead of creating a new one. Name *string `json:"name,omitempty"` } diff --git a/tests/integration/internal/tests/api/sandboxes/filesystem_only_test.go b/tests/integration/internal/tests/api/sandboxes/filesystem_only_test.go index 4822c6e850..fec7d41d55 100644 --- a/tests/integration/internal/tests/api/sandboxes/filesystem_only_test.go +++ b/tests/integration/internal/tests/api/sandboxes/filesystem_only_test.go @@ -168,6 +168,87 @@ func TestSandboxResume_FilesystemOnlyReboots(t *testing.T) { "default workdir after a filesystem-only reboot must be the template user's home") } +// TestSnapshotTemplate_FilesystemOnly verifies the filesystem-only variant of +// the snapshot endpoint (memory:false): the returned template persists only the +// rootfs, so a sandbox created from it cold-boots from the captured filesystem, +// while the source sandbox is memory-checkpointed and resumes with its memory +// intact — an unchanged kernel boot id proves it was not rebooted. +func TestSnapshotTemplate_FilesystemOnly(t *testing.T) { + t.Parallel() + c := setup.GetAPIClient() + ctx := t.Context() + + // Generous timeout: the checkpoint resume goes through placement, and under + // parallel load a short timeout could expire the end time before it runs. + sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithAutoPause(false), utils.WithTimeout(300)) + envdClient := setup.GetEnvdClient(t, ctx) + + bootBefore, err := utils.ExecCommandAsRootWithOutput(t, ctx, sbx, envdClient, + "cat", "/proc/sys/kernel/random/boot_id") + require.NoError(t, err) + bootBefore = strings.TrimSpace(bootBefore) + require.NotEmpty(t, bootBefore) + + const marker = "fs-only-snapshot-marker" + err = utils.ExecCommandAsRoot(t, ctx, sbx, envdClient, + "/bin/sh", "-c", "echo "+marker+" > /home/user/fs-only-snapshot-marker.txt") + require.NoError(t, err) + + memory := false + resp, err := c.PostSandboxesSandboxIDSnapshotsWithResponse(ctx, sbx.SandboxID, + api.PostSandboxesSandboxIDSnapshotsJSONRequestBody{Memory: &memory}, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, resp.StatusCode(), + "filesystem-only snapshot should succeed, body: %s", string(resp.Body)) + require.NotNil(t, resp.JSON201) + snapshot := resp.JSON201 + t.Cleanup(func() { + c.DeleteTemplatesTemplateIDWithResponse(t.Context(), snapshot.SnapshotID, setup.WithAPIKey()) + }) + + // The source sandbox must come back running with its rootfs intact. + res, err := c.GetSandboxesSandboxIDWithResponse(ctx, sbx.SandboxID, setup.WithAPIKey()) + require.NoError(t, err) + require.NotNil(t, res.JSON200) + assert.Equal(t, api.Running, res.JSON200.State) + + got, err := utils.ExecCommandAsRootWithOutput(t, ctx, sbx, envdClient, + "cat", "/home/user/fs-only-snapshot-marker.txt") + require.NoError(t, err) + assert.Equal(t, marker, strings.TrimSpace(got), "rootfs marker must survive the checkpoint") + + // An unchanged boot id proves the source sandbox was memory-resumed, not + // rebooted: only the returned template is filesystem-only. + bootAfter, err := utils.ExecCommandAsRootWithOutput(t, ctx, sbx, envdClient, + "cat", "/proc/sys/kernel/random/boot_id") + require.NoError(t, err) + assert.Equal(t, bootBefore, strings.TrimSpace(bootAfter), + "boot id must not change — a filesystem-only snapshot memory-resumes the source sandbox") + + // A sandbox created from the filesystem-only template cold-boots from the + // captured rootfs, so the marker must be there. + newSbx := utils.SetupSandboxWithCleanup(t, c, + utils.WithTemplateID(snapshot.SnapshotID), + utils.WithAutoPause(false), + utils.WithTimeout(120), + ) + assert.NotEqual(t, sbx.SandboxID, newSbx.SandboxID) + + gotNew, err := utils.ExecCommandAsRootWithOutput(t, ctx, newSbx, envdClient, + "cat", "/home/user/fs-only-snapshot-marker.txt") + require.NoError(t, err) + assert.Equal(t, marker, strings.TrimSpace(gotNew), + "marker must be present in a sandbox created from the filesystem-only snapshot") + + // A fresh boot id in the new sandbox proves it cold-booted from disk rather + // than restoring the source's memory state. + newBoot, err := utils.ExecCommandAsRootWithOutput(t, ctx, newSbx, envdClient, + "cat", "/proc/sys/kernel/random/boot_id") + require.NoError(t, err) + assert.NotEqual(t, bootBefore, strings.TrimSpace(newBoot), + "a sandbox created from a filesystem-only snapshot must cold-boot") +} + // TestSandboxAutoPause_FilesystemOnly verifies the auto-pause filesystem-only // path: a sandbox created with autoPauseMemory=false is auto-paused on timeout // as a filesystem-only snapshot (no memory), so resuming it cold-boots from the