From f4c91ad4b699c9be2bf7bdb8ba8130bb2ba37008 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:08 -0700 Subject: [PATCH 01/14] vendor: add containerd task v2 API + ttrpc for driving the kata shim ateom-cloud-hypervisor speaks the containerd shim v2 task API directly over ttrpc (no containerd daemon), so vendor the task/v2 protos, ttrpc, and their transitive deps. --- .../containerd/containerd/api/LICENSE | 191 ++ LICENSES/github.com/containerd/log/LICENSE | 191 ++ LICENSES/github.com/containerd/ttrpc/LICENSE | 201 ++ go.mod | 3 + go.sum | 6 + .../containerd/containerd/api/LICENSE | 191 ++ .../containerd/api/runtime/task/v2/doc.go | 17 + .../containerd/api/runtime/task/v2/shim.pb.go | 2331 +++++++++++++++++ .../containerd/api/runtime/task/v2/shim.proto | 200 ++ .../api/runtime/task/v2/shim_ttrpc.pb.go | 301 +++ .../containerd/api/types/descriptor.pb.go | 204 ++ .../containerd/api/types/descriptor.proto | 33 + .../containerd/containerd/api/types/doc.go | 17 + .../containerd/api/types/event.pb.go | 205 ++ .../containerd/api/types/event.proto | 33 + .../containerd/api/types/fieldpath.pb.go | 142 + .../containerd/api/types/fieldpath.proto | 42 + .../containerd/api/types/introspection.pb.go | 373 +++ .../containerd/api/types/introspection.proto | 46 + .../containerd/api/types/metrics.pb.go | 191 ++ .../containerd/api/types/metrics.proto | 30 + .../containerd/api/types/mount.pb.go | 414 +++ .../containerd/api/types/mount.proto | 65 + .../containerd/api/types/platform.pb.go | 201 ++ .../containerd/api/types/platform.proto | 31 + .../containerd/api/types/platform_helpers.go | 51 + .../containerd/api/types/sandbox.pb.go | 354 +++ .../containerd/api/types/sandbox.proto | 53 + .../containerd/api/types/task/doc.go | 18 + .../containerd/api/types/task/task.pb.go | 404 +++ .../containerd/api/types/task/task.proto | 55 + .../github.com/containerd/log/.golangci.yml | 30 + vendor/github.com/containerd/log/LICENSE | 191 ++ vendor/github.com/containerd/log/README.md | 17 + vendor/github.com/containerd/log/context.go | 182 ++ .../containerd/ttrpc/.gitattributes | 1 + vendor/github.com/containerd/ttrpc/.gitignore | 13 + .../github.com/containerd/ttrpc/.golangci.yml | 56 + vendor/github.com/containerd/ttrpc/LICENSE | 201 ++ vendor/github.com/containerd/ttrpc/Makefile | 180 ++ .../github.com/containerd/ttrpc/PROTOCOL.md | 240 ++ .../containerd/ttrpc/Protobuild.toml | 28 + vendor/github.com/containerd/ttrpc/README.md | 59 + vendor/github.com/containerd/ttrpc/channel.go | 182 ++ vendor/github.com/containerd/ttrpc/client.go | 571 ++++ vendor/github.com/containerd/ttrpc/codec.go | 43 + vendor/github.com/containerd/ttrpc/config.go | 86 + vendor/github.com/containerd/ttrpc/doc.go | 23 + vendor/github.com/containerd/ttrpc/errors.go | 80 + .../github.com/containerd/ttrpc/handshake.go | 50 + .../containerd/ttrpc/interceptor.go | 65 + .../github.com/containerd/ttrpc/metadata.go | 135 + .../github.com/containerd/ttrpc/request.pb.go | 396 +++ .../github.com/containerd/ttrpc/request.proto | 29 + vendor/github.com/containerd/ttrpc/server.go | 587 +++++ .../github.com/containerd/ttrpc/services.go | 279 ++ vendor/github.com/containerd/ttrpc/stream.go | 84 + .../containerd/ttrpc/stream_server.go | 22 + vendor/github.com/containerd/ttrpc/test.proto | 16 + .../containerd/ttrpc/unixcreds_linux.go | 105 + vendor/modules.txt | 11 + 61 files changed, 10556 insertions(+) create mode 100644 LICENSES/github.com/containerd/containerd/api/LICENSE create mode 100644 LICENSES/github.com/containerd/log/LICENSE create mode 100644 LICENSES/github.com/containerd/ttrpc/LICENSE create mode 100644 vendor/github.com/containerd/containerd/api/LICENSE create mode 100644 vendor/github.com/containerd/containerd/api/runtime/task/v2/doc.go create mode 100644 vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.proto create mode 100644 vendor/github.com/containerd/containerd/api/runtime/task/v2/shim_ttrpc.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/descriptor.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/descriptor.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/doc.go create mode 100644 vendor/github.com/containerd/containerd/api/types/event.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/event.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/fieldpath.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/fieldpath.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/introspection.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/introspection.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/metrics.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/metrics.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/mount.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/mount.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/platform.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/platform.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/platform_helpers.go create mode 100644 vendor/github.com/containerd/containerd/api/types/sandbox.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/sandbox.proto create mode 100644 vendor/github.com/containerd/containerd/api/types/task/doc.go create mode 100644 vendor/github.com/containerd/containerd/api/types/task/task.pb.go create mode 100644 vendor/github.com/containerd/containerd/api/types/task/task.proto create mode 100644 vendor/github.com/containerd/log/.golangci.yml create mode 100644 vendor/github.com/containerd/log/LICENSE create mode 100644 vendor/github.com/containerd/log/README.md create mode 100644 vendor/github.com/containerd/log/context.go create mode 100644 vendor/github.com/containerd/ttrpc/.gitattributes create mode 100644 vendor/github.com/containerd/ttrpc/.gitignore create mode 100644 vendor/github.com/containerd/ttrpc/.golangci.yml create mode 100644 vendor/github.com/containerd/ttrpc/LICENSE create mode 100644 vendor/github.com/containerd/ttrpc/Makefile create mode 100644 vendor/github.com/containerd/ttrpc/PROTOCOL.md create mode 100644 vendor/github.com/containerd/ttrpc/Protobuild.toml create mode 100644 vendor/github.com/containerd/ttrpc/README.md create mode 100644 vendor/github.com/containerd/ttrpc/channel.go create mode 100644 vendor/github.com/containerd/ttrpc/client.go create mode 100644 vendor/github.com/containerd/ttrpc/codec.go create mode 100644 vendor/github.com/containerd/ttrpc/config.go create mode 100644 vendor/github.com/containerd/ttrpc/doc.go create mode 100644 vendor/github.com/containerd/ttrpc/errors.go create mode 100644 vendor/github.com/containerd/ttrpc/handshake.go create mode 100644 vendor/github.com/containerd/ttrpc/interceptor.go create mode 100644 vendor/github.com/containerd/ttrpc/metadata.go create mode 100644 vendor/github.com/containerd/ttrpc/request.pb.go create mode 100644 vendor/github.com/containerd/ttrpc/request.proto create mode 100644 vendor/github.com/containerd/ttrpc/server.go create mode 100644 vendor/github.com/containerd/ttrpc/services.go create mode 100644 vendor/github.com/containerd/ttrpc/stream.go create mode 100644 vendor/github.com/containerd/ttrpc/stream_server.go create mode 100644 vendor/github.com/containerd/ttrpc/test.proto create mode 100644 vendor/github.com/containerd/ttrpc/unixcreds_linux.go diff --git a/LICENSES/github.com/containerd/containerd/api/LICENSE b/LICENSES/github.com/containerd/containerd/api/LICENSE new file mode 100644 index 000000000..584149b6e --- /dev/null +++ b/LICENSES/github.com/containerd/containerd/api/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright The containerd Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSES/github.com/containerd/log/LICENSE b/LICENSES/github.com/containerd/log/LICENSE new file mode 100644 index 000000000..584149b6e --- /dev/null +++ b/LICENSES/github.com/containerd/log/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright The containerd Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSES/github.com/containerd/ttrpc/LICENSE b/LICENSES/github.com/containerd/ttrpc/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSES/github.com/containerd/ttrpc/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/go.mod b/go.mod index 16b41313f..6a83e4594 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( github.com/aws/aws-sdk-go-v2 v1.41.7 github.com/aws/aws-sdk-go-v2/config v1.32.17 github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 + github.com/containerd/containerd/api v1.11.1 + github.com/containerd/ttrpc v1.2.8 github.com/envoyproxy/go-control-plane v0.14.0 github.com/envoyproxy/go-control-plane/envoy v1.37.0 github.com/google/go-cmp v0.7.0 @@ -81,6 +83,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect diff --git a/go.sum b/go.sum index 2151f0ae3..f7acbf349 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,14 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= +github.com/containerd/containerd/api v1.11.1 h1:h8nfoDW9+fNsC/9TwiAHj8B1GzXKtR4eFtkhi/X5RLU= +github.com/containerd/containerd/api v1.11.1/go.mod h1:CaQFRu+N1MtbgL6JDOJLUB1hCKESU1lD6MuTJhgtdlw= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw= github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY= +github.com/containerd/ttrpc v1.2.8 h1:xbVu6D4qF2jihdh9rDVOKqUMiFBQk6YctTdo1zk087Y= +github.com/containerd/ttrpc v1.2.8/go.mod h1:wyZW2K79t4Hfcxl+GUvkZqRBzJlqFFvgEeeWXa42tyE= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/vendor/github.com/containerd/containerd/api/LICENSE b/vendor/github.com/containerd/containerd/api/LICENSE new file mode 100644 index 000000000..584149b6e --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright The containerd Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/doc.go b/vendor/github.com/containerd/containerd/api/runtime/task/v2/doc.go new file mode 100644 index 000000000..f933dd8d4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/doc.go @@ -0,0 +1,17 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package task diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.pb.go b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.pb.go new file mode 100644 index 000000000..e62f4e70d --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.pb.go @@ -0,0 +1,2331 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: runtime/task/v2/shim.proto + +package task + +import ( + types "github.com/containerd/containerd/api/types" + task "github.com/containerd/containerd/api/types/task" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CreateTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Bundle string `protobuf:"bytes,2,opt,name=bundle,proto3" json:"bundle,omitempty"` + Rootfs []*types.Mount `protobuf:"bytes,3,rep,name=rootfs,proto3" json:"rootfs,omitempty"` + Terminal bool `protobuf:"varint,4,opt,name=terminal,proto3" json:"terminal,omitempty"` + Stdin string `protobuf:"bytes,5,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,6,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,7,opt,name=stderr,proto3" json:"stderr,omitempty"` + Checkpoint string `protobuf:"bytes,8,opt,name=checkpoint,proto3" json:"checkpoint,omitempty"` + ParentCheckpoint string `protobuf:"bytes,9,opt,name=parent_checkpoint,json=parentCheckpoint,proto3" json:"parent_checkpoint,omitempty"` + Options *anypb.Any `protobuf:"bytes,10,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *CreateTaskRequest) Reset() { + *x = CreateTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskRequest) ProtoMessage() {} + +func (x *CreateTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskRequest.ProtoReflect.Descriptor instead. +func (*CreateTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{0} +} + +func (x *CreateTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CreateTaskRequest) GetBundle() string { + if x != nil { + return x.Bundle + } + return "" +} + +func (x *CreateTaskRequest) GetRootfs() []*types.Mount { + if x != nil { + return x.Rootfs + } + return nil +} + +func (x *CreateTaskRequest) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *CreateTaskRequest) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *CreateTaskRequest) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *CreateTaskRequest) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *CreateTaskRequest) GetCheckpoint() string { + if x != nil { + return x.Checkpoint + } + return "" +} + +func (x *CreateTaskRequest) GetParentCheckpoint() string { + if x != nil { + return x.ParentCheckpoint + } + return "" +} + +func (x *CreateTaskRequest) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +type CreateTaskResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` +} + +func (x *CreateTaskResponse) Reset() { + *x = CreateTaskResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTaskResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskResponse) ProtoMessage() {} + +func (x *CreateTaskResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskResponse.ProtoReflect.Descriptor instead. +func (*CreateTaskResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateTaskResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +type DeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{2} +} + +func (x *DeleteRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *DeleteRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type DeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` + ExitStatus uint32 `protobuf:"varint,2,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` +} + +func (x *DeleteResponse) Reset() { + *x = DeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResponse) ProtoMessage() {} + +func (x *DeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{3} +} + +func (x *DeleteResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *DeleteResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *DeleteResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +type ExecProcessRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Terminal bool `protobuf:"varint,3,opt,name=terminal,proto3" json:"terminal,omitempty"` + Stdin string `protobuf:"bytes,4,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,5,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,6,opt,name=stderr,proto3" json:"stderr,omitempty"` + Spec *anypb.Any `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` +} + +func (x *ExecProcessRequest) Reset() { + *x = ExecProcessRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecProcessRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecProcessRequest) ProtoMessage() {} + +func (x *ExecProcessRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecProcessRequest.ProtoReflect.Descriptor instead. +func (*ExecProcessRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{4} +} + +func (x *ExecProcessRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ExecProcessRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *ExecProcessRequest) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *ExecProcessRequest) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *ExecProcessRequest) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *ExecProcessRequest) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *ExecProcessRequest) GetSpec() *anypb.Any { + if x != nil { + return x.Spec + } + return nil +} + +type ExecProcessResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ExecProcessResponse) Reset() { + *x = ExecProcessResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecProcessResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecProcessResponse) ProtoMessage() {} + +func (x *ExecProcessResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecProcessResponse.ProtoReflect.Descriptor instead. +func (*ExecProcessResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{5} +} + +type ResizePtyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Width uint32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` + Height uint32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` +} + +func (x *ResizePtyRequest) Reset() { + *x = ResizePtyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResizePtyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResizePtyRequest) ProtoMessage() {} + +func (x *ResizePtyRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResizePtyRequest.ProtoReflect.Descriptor instead. +func (*ResizePtyRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{6} +} + +func (x *ResizePtyRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ResizePtyRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *ResizePtyRequest) GetWidth() uint32 { + if x != nil { + return x.Width + } + return 0 +} + +func (x *ResizePtyRequest) GetHeight() uint32 { + if x != nil { + return x.Height + } + return 0 +} + +type StateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StateRequest) Reset() { + *x = StateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateRequest) ProtoMessage() {} + +func (x *StateRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateRequest.ProtoReflect.Descriptor instead. +func (*StateRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{7} +} + +func (x *StateRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StateRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type StateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Bundle string `protobuf:"bytes,2,opt,name=bundle,proto3" json:"bundle,omitempty"` + Pid uint32 `protobuf:"varint,3,opt,name=pid,proto3" json:"pid,omitempty"` + Status task.Status `protobuf:"varint,4,opt,name=status,proto3,enum=containerd.v1.types.Status" json:"status,omitempty"` + Stdin string `protobuf:"bytes,5,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,6,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,7,opt,name=stderr,proto3" json:"stderr,omitempty"` + Terminal bool `protobuf:"varint,8,opt,name=terminal,proto3" json:"terminal,omitempty"` + ExitStatus uint32 `protobuf:"varint,9,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` + ExecID string `protobuf:"bytes,11,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StateResponse) Reset() { + *x = StateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateResponse) ProtoMessage() {} + +func (x *StateResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateResponse.ProtoReflect.Descriptor instead. +func (*StateResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{8} +} + +func (x *StateResponse) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StateResponse) GetBundle() string { + if x != nil { + return x.Bundle + } + return "" +} + +func (x *StateResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *StateResponse) GetStatus() task.Status { + if x != nil { + return x.Status + } + return task.Status(0) +} + +func (x *StateResponse) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *StateResponse) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *StateResponse) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *StateResponse) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *StateResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *StateResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +func (x *StateResponse) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type KillRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Signal uint32 `protobuf:"varint,3,opt,name=signal,proto3" json:"signal,omitempty"` + All bool `protobuf:"varint,4,opt,name=all,proto3" json:"all,omitempty"` +} + +func (x *KillRequest) Reset() { + *x = KillRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KillRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KillRequest) ProtoMessage() {} + +func (x *KillRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KillRequest.ProtoReflect.Descriptor instead. +func (*KillRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{9} +} + +func (x *KillRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *KillRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *KillRequest) GetSignal() uint32 { + if x != nil { + return x.Signal + } + return 0 +} + +func (x *KillRequest) GetAll() bool { + if x != nil { + return x.All + } + return false +} + +type CloseIORequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` + Stdin bool `protobuf:"varint,3,opt,name=stdin,proto3" json:"stdin,omitempty"` +} + +func (x *CloseIORequest) Reset() { + *x = CloseIORequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseIORequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseIORequest) ProtoMessage() {} + +func (x *CloseIORequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseIORequest.ProtoReflect.Descriptor instead. +func (*CloseIORequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{10} +} + +func (x *CloseIORequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CloseIORequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +func (x *CloseIORequest) GetStdin() bool { + if x != nil { + return x.Stdin + } + return false +} + +type PidsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *PidsRequest) Reset() { + *x = PidsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PidsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PidsRequest) ProtoMessage() {} + +func (x *PidsRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PidsRequest.ProtoReflect.Descriptor instead. +func (*PidsRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{11} +} + +func (x *PidsRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type PidsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Processes []*task.ProcessInfo `protobuf:"bytes,1,rep,name=processes,proto3" json:"processes,omitempty"` +} + +func (x *PidsResponse) Reset() { + *x = PidsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PidsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PidsResponse) ProtoMessage() {} + +func (x *PidsResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PidsResponse.ProtoReflect.Descriptor instead. +func (*PidsResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{12} +} + +func (x *PidsResponse) GetProcesses() []*task.ProcessInfo { + if x != nil { + return x.Processes + } + return nil +} + +type CheckpointTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Options *anypb.Any `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *CheckpointTaskRequest) Reset() { + *x = CheckpointTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckpointTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckpointTaskRequest) ProtoMessage() {} + +func (x *CheckpointTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckpointTaskRequest.ProtoReflect.Descriptor instead. +func (*CheckpointTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{13} +} + +func (x *CheckpointTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *CheckpointTaskRequest) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *CheckpointTaskRequest) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +type UpdateTaskRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Resources *anypb.Any `protobuf:"bytes,2,opt,name=resources,proto3" json:"resources,omitempty"` + Annotations map[string]string `protobuf:"bytes,3,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *UpdateTaskRequest) Reset() { + *x = UpdateTaskRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTaskRequest) ProtoMessage() {} + +func (x *UpdateTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTaskRequest.ProtoReflect.Descriptor instead. +func (*UpdateTaskRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{14} +} + +func (x *UpdateTaskRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *UpdateTaskRequest) GetResources() *anypb.Any { + if x != nil { + return x.Resources + } + return nil +} + +func (x *UpdateTaskRequest) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +type StartRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *StartRequest) Reset() { + *x = StartRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartRequest) ProtoMessage() {} + +func (x *StartRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartRequest.ProtoReflect.Descriptor instead. +func (*StartRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{15} +} + +func (x *StartRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *StartRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type StartResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` +} + +func (x *StartResponse) Reset() { + *x = StartResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartResponse) ProtoMessage() {} + +func (x *StartResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartResponse.ProtoReflect.Descriptor instead. +func (*StartResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{16} +} + +func (x *StartResponse) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +type WaitRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExecID string `protobuf:"bytes,2,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` +} + +func (x *WaitRequest) Reset() { + *x = WaitRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WaitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitRequest) ProtoMessage() {} + +func (x *WaitRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitRequest.ProtoReflect.Descriptor instead. +func (*WaitRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{17} +} + +func (x *WaitRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *WaitRequest) GetExecID() string { + if x != nil { + return x.ExecID + } + return "" +} + +type WaitResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ExitStatus uint32 `protobuf:"varint,1,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` +} + +func (x *WaitResponse) Reset() { + *x = WaitResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WaitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitResponse) ProtoMessage() {} + +func (x *WaitResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitResponse.ProtoReflect.Descriptor instead. +func (*WaitResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{18} +} + +func (x *WaitResponse) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *WaitResponse) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +type StatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *StatsRequest) Reset() { + *x = StatsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsRequest) ProtoMessage() {} + +func (x *StatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead. +func (*StatsRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{19} +} + +func (x *StatsRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type StatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats *anypb.Any `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *StatsResponse) Reset() { + *x = StatsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsResponse) ProtoMessage() {} + +func (x *StatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead. +func (*StatsResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{20} +} + +func (x *StatsResponse) GetStats() *anypb.Any { + if x != nil { + return x.Stats + } + return nil +} + +type ConnectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ConnectRequest) Reset() { + *x = ConnectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectRequest) ProtoMessage() {} + +func (x *ConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead. +func (*ConnectRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{21} +} + +func (x *ConnectRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type ConnectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ShimPid uint32 `protobuf:"varint,1,opt,name=shim_pid,json=shimPid,proto3" json:"shim_pid,omitempty"` + TaskPid uint32 `protobuf:"varint,2,opt,name=task_pid,json=taskPid,proto3" json:"task_pid,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *ConnectResponse) Reset() { + *x = ConnectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectResponse) ProtoMessage() {} + +func (x *ConnectResponse) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectResponse.ProtoReflect.Descriptor instead. +func (*ConnectResponse) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{22} +} + +func (x *ConnectResponse) GetShimPid() uint32 { + if x != nil { + return x.ShimPid + } + return 0 +} + +func (x *ConnectResponse) GetTaskPid() uint32 { + if x != nil { + return x.TaskPid + } + return 0 +} + +func (x *ConnectResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type ShutdownRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Now bool `protobuf:"varint,2,opt,name=now,proto3" json:"now,omitempty"` +} + +func (x *ShutdownRequest) Reset() { + *x = ShutdownRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShutdownRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShutdownRequest) ProtoMessage() {} + +func (x *ShutdownRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShutdownRequest.ProtoReflect.Descriptor instead. +func (*ShutdownRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{23} +} + +func (x *ShutdownRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ShutdownRequest) GetNow() bool { + if x != nil { + return x.Now + } + return false +} + +type PauseRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *PauseRequest) Reset() { + *x = PauseRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PauseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PauseRequest) ProtoMessage() {} + +func (x *PauseRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PauseRequest.ProtoReflect.Descriptor instead. +func (*PauseRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{24} +} + +func (x *PauseRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type ResumeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ResumeRequest) Reset() { + *x = ResumeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runtime_task_v2_shim_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResumeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResumeRequest) ProtoMessage() {} + +func (x *ResumeRequest) ProtoReflect() protoreflect.Message { + mi := &file_runtime_task_v2_shim_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResumeRequest.ProtoReflect.Descriptor instead. +func (*ResumeRequest) Descriptor() ([]byte, []int) { + return file_runtime_task_v2_shim_proto_rawDescGZIP(), []int{25} +} + +func (x *ResumeRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +var File_runtime_task_v2_shim_proto protoreflect.FileDescriptor + +var file_runtime_task_v2_shim_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x76, + 0x32, 0x2f, 0x73, 0x68, 0x69, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, + 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, + 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xcb, 0x02, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x74, + 0x66, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, + 0x74, 0x64, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x64, 0x65, 0x72, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x10, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x26, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x38, 0x0a, 0x0d, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, + 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, + 0x63, 0x49, 0x64, 0x22, 0x7c, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, + 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 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, 0x08, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x22, 0xc9, 0x01, 0x0a, 0x12, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, + 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, + 0x64, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, + 0x65, 0x72, 0x72, 0x12, 0x28, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x15, 0x0a, + 0x13, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x69, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, + 0x37, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0xd3, 0x02, 0x0a, 0x0d, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x03, 0x70, 0x69, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x64, + 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, + 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, + 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 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, 0x08, 0x65, 0x78, 0x69, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x60, + 0x0a, 0x0b, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x12, 0x10, + 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6c, 0x6c, + 0x22, 0x4f, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x64, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, + 0x6e, 0x22, 0x1d, 0x0a, 0x0b, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x4e, 0x0a, 0x0c, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x22, 0x6b, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, + 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf1, 0x01, + 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, + 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 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, 0x37, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x21, 0x0a, 0x0d, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x36, 0x0a, + 0x0b, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x65, 0x78, 0x65, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, + 0x78, 0x65, 0x63, 0x49, 0x64, 0x22, 0x68, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 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, 0x08, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, + 0x1e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x3b, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2a, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x20, 0x0a, 0x0e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x61, + 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x68, 0x69, 0x6d, 0x50, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x74, 0x61, 0x73, 0x6b, 0x50, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x33, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x03, 0x6e, 0x6f, 0x77, 0x22, 0x1e, 0x0a, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0x8a, 0x0a, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, + 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, + 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, + 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x04, 0x50, 0x69, 0x64, 0x73, 0x12, 0x1f, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x69, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x41, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, + 0x50, 0x61, 0x75, 0x73, 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, 0x43, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x21, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 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, 0x4f, 0x0a, 0x0a, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x29, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, 0x61, 0x73, 0x6b, 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, 0x3f, 0x0a, 0x04, 0x4b, 0x69, + 0x6c, 0x6c, 0x12, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x4b, 0x69, 0x6c, 0x6c, 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, 0x46, 0x0a, 0x04, 0x45, + 0x78, 0x65, 0x63, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 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, 0x49, 0x0a, 0x09, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, 0x79, + 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, + 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x50, 0x74, 0x79, 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, 0x45, + 0x0a, 0x07, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x4f, 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, 0x47, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 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, 0x49, + 0x0a, 0x04, 0x57, 0x61, 0x69, 0x74, 0x12, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x61, 0x69, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x61, 0x69, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x12, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x53, + 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x68, 0x75, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 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, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x76, 0x32, 0x3b, 0x74, 0x61, 0x73, + 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_runtime_task_v2_shim_proto_rawDescOnce sync.Once + file_runtime_task_v2_shim_proto_rawDescData = file_runtime_task_v2_shim_proto_rawDesc +) + +func file_runtime_task_v2_shim_proto_rawDescGZIP() []byte { + file_runtime_task_v2_shim_proto_rawDescOnce.Do(func() { + file_runtime_task_v2_shim_proto_rawDescData = protoimpl.X.CompressGZIP(file_runtime_task_v2_shim_proto_rawDescData) + }) + return file_runtime_task_v2_shim_proto_rawDescData +} + +var file_runtime_task_v2_shim_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_runtime_task_v2_shim_proto_goTypes = []interface{}{ + (*CreateTaskRequest)(nil), // 0: containerd.task.v2.CreateTaskRequest + (*CreateTaskResponse)(nil), // 1: containerd.task.v2.CreateTaskResponse + (*DeleteRequest)(nil), // 2: containerd.task.v2.DeleteRequest + (*DeleteResponse)(nil), // 3: containerd.task.v2.DeleteResponse + (*ExecProcessRequest)(nil), // 4: containerd.task.v2.ExecProcessRequest + (*ExecProcessResponse)(nil), // 5: containerd.task.v2.ExecProcessResponse + (*ResizePtyRequest)(nil), // 6: containerd.task.v2.ResizePtyRequest + (*StateRequest)(nil), // 7: containerd.task.v2.StateRequest + (*StateResponse)(nil), // 8: containerd.task.v2.StateResponse + (*KillRequest)(nil), // 9: containerd.task.v2.KillRequest + (*CloseIORequest)(nil), // 10: containerd.task.v2.CloseIORequest + (*PidsRequest)(nil), // 11: containerd.task.v2.PidsRequest + (*PidsResponse)(nil), // 12: containerd.task.v2.PidsResponse + (*CheckpointTaskRequest)(nil), // 13: containerd.task.v2.CheckpointTaskRequest + (*UpdateTaskRequest)(nil), // 14: containerd.task.v2.UpdateTaskRequest + (*StartRequest)(nil), // 15: containerd.task.v2.StartRequest + (*StartResponse)(nil), // 16: containerd.task.v2.StartResponse + (*WaitRequest)(nil), // 17: containerd.task.v2.WaitRequest + (*WaitResponse)(nil), // 18: containerd.task.v2.WaitResponse + (*StatsRequest)(nil), // 19: containerd.task.v2.StatsRequest + (*StatsResponse)(nil), // 20: containerd.task.v2.StatsResponse + (*ConnectRequest)(nil), // 21: containerd.task.v2.ConnectRequest + (*ConnectResponse)(nil), // 22: containerd.task.v2.ConnectResponse + (*ShutdownRequest)(nil), // 23: containerd.task.v2.ShutdownRequest + (*PauseRequest)(nil), // 24: containerd.task.v2.PauseRequest + (*ResumeRequest)(nil), // 25: containerd.task.v2.ResumeRequest + nil, // 26: containerd.task.v2.UpdateTaskRequest.AnnotationsEntry + (*types.Mount)(nil), // 27: containerd.types.Mount + (*anypb.Any)(nil), // 28: google.protobuf.Any + (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp + (task.Status)(0), // 30: containerd.v1.types.Status + (*task.ProcessInfo)(nil), // 31: containerd.v1.types.ProcessInfo + (*emptypb.Empty)(nil), // 32: google.protobuf.Empty +} +var file_runtime_task_v2_shim_proto_depIdxs = []int32{ + 27, // 0: containerd.task.v2.CreateTaskRequest.rootfs:type_name -> containerd.types.Mount + 28, // 1: containerd.task.v2.CreateTaskRequest.options:type_name -> google.protobuf.Any + 29, // 2: containerd.task.v2.DeleteResponse.exited_at:type_name -> google.protobuf.Timestamp + 28, // 3: containerd.task.v2.ExecProcessRequest.spec:type_name -> google.protobuf.Any + 30, // 4: containerd.task.v2.StateResponse.status:type_name -> containerd.v1.types.Status + 29, // 5: containerd.task.v2.StateResponse.exited_at:type_name -> google.protobuf.Timestamp + 31, // 6: containerd.task.v2.PidsResponse.processes:type_name -> containerd.v1.types.ProcessInfo + 28, // 7: containerd.task.v2.CheckpointTaskRequest.options:type_name -> google.protobuf.Any + 28, // 8: containerd.task.v2.UpdateTaskRequest.resources:type_name -> google.protobuf.Any + 26, // 9: containerd.task.v2.UpdateTaskRequest.annotations:type_name -> containerd.task.v2.UpdateTaskRequest.AnnotationsEntry + 29, // 10: containerd.task.v2.WaitResponse.exited_at:type_name -> google.protobuf.Timestamp + 28, // 11: containerd.task.v2.StatsResponse.stats:type_name -> google.protobuf.Any + 7, // 12: containerd.task.v2.Task.State:input_type -> containerd.task.v2.StateRequest + 0, // 13: containerd.task.v2.Task.Create:input_type -> containerd.task.v2.CreateTaskRequest + 15, // 14: containerd.task.v2.Task.Start:input_type -> containerd.task.v2.StartRequest + 2, // 15: containerd.task.v2.Task.Delete:input_type -> containerd.task.v2.DeleteRequest + 11, // 16: containerd.task.v2.Task.Pids:input_type -> containerd.task.v2.PidsRequest + 24, // 17: containerd.task.v2.Task.Pause:input_type -> containerd.task.v2.PauseRequest + 25, // 18: containerd.task.v2.Task.Resume:input_type -> containerd.task.v2.ResumeRequest + 13, // 19: containerd.task.v2.Task.Checkpoint:input_type -> containerd.task.v2.CheckpointTaskRequest + 9, // 20: containerd.task.v2.Task.Kill:input_type -> containerd.task.v2.KillRequest + 4, // 21: containerd.task.v2.Task.Exec:input_type -> containerd.task.v2.ExecProcessRequest + 6, // 22: containerd.task.v2.Task.ResizePty:input_type -> containerd.task.v2.ResizePtyRequest + 10, // 23: containerd.task.v2.Task.CloseIO:input_type -> containerd.task.v2.CloseIORequest + 14, // 24: containerd.task.v2.Task.Update:input_type -> containerd.task.v2.UpdateTaskRequest + 17, // 25: containerd.task.v2.Task.Wait:input_type -> containerd.task.v2.WaitRequest + 19, // 26: containerd.task.v2.Task.Stats:input_type -> containerd.task.v2.StatsRequest + 21, // 27: containerd.task.v2.Task.Connect:input_type -> containerd.task.v2.ConnectRequest + 23, // 28: containerd.task.v2.Task.Shutdown:input_type -> containerd.task.v2.ShutdownRequest + 8, // 29: containerd.task.v2.Task.State:output_type -> containerd.task.v2.StateResponse + 1, // 30: containerd.task.v2.Task.Create:output_type -> containerd.task.v2.CreateTaskResponse + 16, // 31: containerd.task.v2.Task.Start:output_type -> containerd.task.v2.StartResponse + 3, // 32: containerd.task.v2.Task.Delete:output_type -> containerd.task.v2.DeleteResponse + 12, // 33: containerd.task.v2.Task.Pids:output_type -> containerd.task.v2.PidsResponse + 32, // 34: containerd.task.v2.Task.Pause:output_type -> google.protobuf.Empty + 32, // 35: containerd.task.v2.Task.Resume:output_type -> google.protobuf.Empty + 32, // 36: containerd.task.v2.Task.Checkpoint:output_type -> google.protobuf.Empty + 32, // 37: containerd.task.v2.Task.Kill:output_type -> google.protobuf.Empty + 32, // 38: containerd.task.v2.Task.Exec:output_type -> google.protobuf.Empty + 32, // 39: containerd.task.v2.Task.ResizePty:output_type -> google.protobuf.Empty + 32, // 40: containerd.task.v2.Task.CloseIO:output_type -> google.protobuf.Empty + 32, // 41: containerd.task.v2.Task.Update:output_type -> google.protobuf.Empty + 18, // 42: containerd.task.v2.Task.Wait:output_type -> containerd.task.v2.WaitResponse + 20, // 43: containerd.task.v2.Task.Stats:output_type -> containerd.task.v2.StatsResponse + 22, // 44: containerd.task.v2.Task.Connect:output_type -> containerd.task.v2.ConnectResponse + 32, // 45: containerd.task.v2.Task.Shutdown:output_type -> google.protobuf.Empty + 29, // [29:46] is the sub-list for method output_type + 12, // [12:29] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_runtime_task_v2_shim_proto_init() } +func file_runtime_task_v2_shim_proto_init() { + if File_runtime_task_v2_shim_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_runtime_task_v2_shim_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTaskResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecProcessRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecProcessResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResizePtyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KillRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseIORequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PidsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PidsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckpointTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTaskRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WaitRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WaitResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShutdownRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PauseRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runtime_task_v2_shim_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResumeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_runtime_task_v2_shim_proto_rawDesc, + NumEnums: 0, + NumMessages: 27, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_runtime_task_v2_shim_proto_goTypes, + DependencyIndexes: file_runtime_task_v2_shim_proto_depIdxs, + MessageInfos: file_runtime_task_v2_shim_proto_msgTypes, + }.Build() + File_runtime_task_v2_shim_proto = out.File + file_runtime_task_v2_shim_proto_rawDesc = nil + file_runtime_task_v2_shim_proto_goTypes = nil + file_runtime_task_v2_shim_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.proto b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.proto new file mode 100644 index 000000000..6d9c36e03 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim.proto @@ -0,0 +1,200 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.task.v2; + +import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "types/mount.proto"; +import "types/task/task.proto"; + +option go_package = "github.com/containerd/containerd/api/runtime/task/v2;task"; + +// Shim service is launched for each container and is responsible for owning the IO +// for the container and its additional processes. The shim is also the parent of +// each container and allows reattaching to the IO and receiving the exit status +// for the container processes. +service Task { + rpc State(StateRequest) returns (StateResponse); + rpc Create(CreateTaskRequest) returns (CreateTaskResponse); + rpc Start(StartRequest) returns (StartResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc Pids(PidsRequest) returns (PidsResponse); + rpc Pause(PauseRequest) returns (google.protobuf.Empty); + rpc Resume(ResumeRequest) returns (google.protobuf.Empty); + rpc Checkpoint(CheckpointTaskRequest) returns (google.protobuf.Empty); + rpc Kill(KillRequest) returns (google.protobuf.Empty); + rpc Exec(ExecProcessRequest) returns (google.protobuf.Empty); + rpc ResizePty(ResizePtyRequest) returns (google.protobuf.Empty); + rpc CloseIO(CloseIORequest) returns (google.protobuf.Empty); + rpc Update(UpdateTaskRequest) returns (google.protobuf.Empty); + rpc Wait(WaitRequest) returns (WaitResponse); + rpc Stats(StatsRequest) returns (StatsResponse); + rpc Connect(ConnectRequest) returns (ConnectResponse); + rpc Shutdown(ShutdownRequest) returns (google.protobuf.Empty); +} + +message CreateTaskRequest { + string id = 1; + string bundle = 2; + repeated containerd.types.Mount rootfs = 3; + bool terminal = 4; + string stdin = 5; + string stdout = 6; + string stderr = 7; + string checkpoint = 8; + string parent_checkpoint = 9; + google.protobuf.Any options = 10; +} + +message CreateTaskResponse { + uint32 pid = 1; +} + +message DeleteRequest { + string id = 1; + string exec_id = 2; +} + +message DeleteResponse { + uint32 pid = 1; + uint32 exit_status = 2; + google.protobuf.Timestamp exited_at = 3; +} + +message ExecProcessRequest { + string id = 1; + string exec_id = 2; + bool terminal = 3; + string stdin = 4; + string stdout = 5; + string stderr = 6; + google.protobuf.Any spec = 7; +} + +message ExecProcessResponse {} + +message ResizePtyRequest { + string id = 1; + string exec_id = 2; + uint32 width = 3; + uint32 height = 4; +} + +message StateRequest { + string id = 1; + string exec_id = 2; +} + +message StateResponse { + string id = 1; + string bundle = 2; + uint32 pid = 3; + containerd.v1.types.Status status = 4; + string stdin = 5; + string stdout = 6; + string stderr = 7; + bool terminal = 8; + uint32 exit_status = 9; + google.protobuf.Timestamp exited_at = 10; + string exec_id = 11; +} + +message KillRequest { + string id = 1; + string exec_id = 2; + uint32 signal = 3; + bool all = 4; +} + +message CloseIORequest { + string id = 1; + string exec_id = 2; + bool stdin = 3; +} + +message PidsRequest { + string id = 1; +} + +message PidsResponse { + repeated containerd.v1.types.ProcessInfo processes = 1; +} + +message CheckpointTaskRequest { + string id = 1; + string path = 2; + google.protobuf.Any options = 3; +} + +message UpdateTaskRequest { + string id = 1; + google.protobuf.Any resources = 2; + map annotations = 3; +} + +message StartRequest { + string id = 1; + string exec_id = 2; +} + +message StartResponse { + uint32 pid = 1; +} + +message WaitRequest { + string id = 1; + string exec_id = 2; +} + +message WaitResponse { + uint32 exit_status = 1; + google.protobuf.Timestamp exited_at = 2; +} + +message StatsRequest { + string id = 1; +} + +message StatsResponse { + google.protobuf.Any stats = 1; +} + +message ConnectRequest { + string id = 1; +} + +message ConnectResponse { + uint32 shim_pid = 1; + uint32 task_pid = 2; + string version = 3; +} + +message ShutdownRequest { + string id = 1; + bool now = 2; +} + +message PauseRequest { + string id = 1; +} + +message ResumeRequest { + string id = 1; +} diff --git a/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim_ttrpc.pb.go b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim_ttrpc.pb.go new file mode 100644 index 000000000..822d7dadd --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/runtime/task/v2/shim_ttrpc.pb.go @@ -0,0 +1,301 @@ +// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT. +// source: runtime/task/v2/shim.proto +package task + +import ( + context "context" + ttrpc "github.com/containerd/ttrpc" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +type TTRPCTaskService interface { + State(context.Context, *StateRequest) (*StateResponse, error) + Create(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) + Start(context.Context, *StartRequest) (*StartResponse, error) + Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) + Pids(context.Context, *PidsRequest) (*PidsResponse, error) + Pause(context.Context, *PauseRequest) (*emptypb.Empty, error) + Resume(context.Context, *ResumeRequest) (*emptypb.Empty, error) + Checkpoint(context.Context, *CheckpointTaskRequest) (*emptypb.Empty, error) + Kill(context.Context, *KillRequest) (*emptypb.Empty, error) + Exec(context.Context, *ExecProcessRequest) (*emptypb.Empty, error) + ResizePty(context.Context, *ResizePtyRequest) (*emptypb.Empty, error) + CloseIO(context.Context, *CloseIORequest) (*emptypb.Empty, error) + Update(context.Context, *UpdateTaskRequest) (*emptypb.Empty, error) + Wait(context.Context, *WaitRequest) (*WaitResponse, error) + Stats(context.Context, *StatsRequest) (*StatsResponse, error) + Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) + Shutdown(context.Context, *ShutdownRequest) (*emptypb.Empty, error) +} + +func RegisterTTRPCTaskService(srv *ttrpc.Server, svc TTRPCTaskService) { + srv.RegisterService("containerd.task.v2.Task", &ttrpc.ServiceDesc{ + Methods: map[string]ttrpc.Method{ + "State": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StateRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.State(ctx, &req) + }, + "Create": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CreateTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Create(ctx, &req) + }, + "Start": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StartRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Start(ctx, &req) + }, + "Delete": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req DeleteRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Delete(ctx, &req) + }, + "Pids": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req PidsRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Pids(ctx, &req) + }, + "Pause": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req PauseRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Pause(ctx, &req) + }, + "Resume": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ResumeRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Resume(ctx, &req) + }, + "Checkpoint": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CheckpointTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Checkpoint(ctx, &req) + }, + "Kill": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req KillRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Kill(ctx, &req) + }, + "Exec": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ExecProcessRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Exec(ctx, &req) + }, + "ResizePty": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ResizePtyRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.ResizePty(ctx, &req) + }, + "CloseIO": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req CloseIORequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.CloseIO(ctx, &req) + }, + "Update": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req UpdateTaskRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Update(ctx, &req) + }, + "Wait": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req WaitRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Wait(ctx, &req) + }, + "Stats": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req StatsRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Stats(ctx, &req) + }, + "Connect": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ConnectRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Connect(ctx, &req) + }, + "Shutdown": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + var req ShutdownRequest + if err := unmarshal(&req); err != nil { + return nil, err + } + return svc.Shutdown(ctx, &req) + }, + }, + }) +} + +type ttrpctaskClient struct { + client *ttrpc.Client +} + +func NewTTRPCTaskClient(client *ttrpc.Client) TTRPCTaskService { + return &ttrpctaskClient{ + client: client, + } +} + +func (c *ttrpctaskClient) State(ctx context.Context, req *StateRequest) (*StateResponse, error) { + var resp StateResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "State", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Create(ctx context.Context, req *CreateTaskRequest) (*CreateTaskResponse, error) { + var resp CreateTaskResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Create", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Start(ctx context.Context, req *StartRequest) (*StartResponse, error) { + var resp StartResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Start", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) { + var resp DeleteResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Delete", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Pids(ctx context.Context, req *PidsRequest) (*PidsResponse, error) { + var resp PidsResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Pids", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Pause(ctx context.Context, req *PauseRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Pause", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Resume(ctx context.Context, req *ResumeRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Resume", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Checkpoint(ctx context.Context, req *CheckpointTaskRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Checkpoint", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Kill(ctx context.Context, req *KillRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Kill", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Exec(ctx context.Context, req *ExecProcessRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Exec", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) ResizePty(ctx context.Context, req *ResizePtyRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "ResizePty", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) CloseIO(ctx context.Context, req *CloseIORequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "CloseIO", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Update(ctx context.Context, req *UpdateTaskRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Update", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Wait(ctx context.Context, req *WaitRequest) (*WaitResponse, error) { + var resp WaitResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Wait", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Stats(ctx context.Context, req *StatsRequest) (*StatsResponse, error) { + var resp StatsResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Stats", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Connect(ctx context.Context, req *ConnectRequest) (*ConnectResponse, error) { + var resp ConnectResponse + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Connect", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} + +func (c *ttrpctaskClient) Shutdown(ctx context.Context, req *ShutdownRequest) (*emptypb.Empty, error) { + var resp emptypb.Empty + if err := c.client.Call(ctx, "containerd.task.v2.Task", "Shutdown", req, &resp); err != nil { + return nil, err + } + return &resp, nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/descriptor.pb.go b/vendor/github.com/containerd/containerd/api/types/descriptor.pb.go new file mode 100644 index 000000000..ebc43750d --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/descriptor.pb.go @@ -0,0 +1,204 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/descriptor.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Descriptor describes a blob in a content store. +// +// This descriptor can be used to reference content from an +// oci descriptor found in a manifest. +// See https://godoc.org/github.com/opencontainers/image-spec/specs-go/v1#Descriptor +type Descriptor struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MediaType string `protobuf:"bytes,1,opt,name=media_type,json=mediaType,proto3" json:"media_type,omitempty"` + Digest string `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"` + Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` + Annotations map[string]string `protobuf:"bytes,5,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Descriptor) Reset() { + *x = Descriptor{} + if protoimpl.UnsafeEnabled { + mi := &file_types_descriptor_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Descriptor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Descriptor) ProtoMessage() {} + +func (x *Descriptor) ProtoReflect() protoreflect.Message { + mi := &file_types_descriptor_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Descriptor.ProtoReflect.Descriptor instead. +func (*Descriptor) Descriptor() ([]byte, []int) { + return file_types_descriptor_proto_rawDescGZIP(), []int{0} +} + +func (x *Descriptor) GetMediaType() string { + if x != nil { + return x.MediaType + } + return "" +} + +func (x *Descriptor) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *Descriptor) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *Descriptor) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +var File_types_descriptor_proto protoreflect.FileDescriptor + +var file_types_descriptor_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xe8, 0x01, 0x0a, 0x0a, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x64, + 0x69, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, + 0x65, 0x64, 0x69, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, + 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x4f, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 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, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_types_descriptor_proto_rawDescOnce sync.Once + file_types_descriptor_proto_rawDescData = file_types_descriptor_proto_rawDesc +) + +func file_types_descriptor_proto_rawDescGZIP() []byte { + file_types_descriptor_proto_rawDescOnce.Do(func() { + file_types_descriptor_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_descriptor_proto_rawDescData) + }) + return file_types_descriptor_proto_rawDescData +} + +var file_types_descriptor_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_types_descriptor_proto_goTypes = []interface{}{ + (*Descriptor)(nil), // 0: containerd.types.Descriptor + nil, // 1: containerd.types.Descriptor.AnnotationsEntry +} +var file_types_descriptor_proto_depIdxs = []int32{ + 1, // 0: containerd.types.Descriptor.annotations:type_name -> containerd.types.Descriptor.AnnotationsEntry + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_types_descriptor_proto_init() } +func file_types_descriptor_proto_init() { + if File_types_descriptor_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_descriptor_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Descriptor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_descriptor_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_descriptor_proto_goTypes, + DependencyIndexes: file_types_descriptor_proto_depIdxs, + MessageInfos: file_types_descriptor_proto_msgTypes, + }.Build() + File_types_descriptor_proto = out.File + file_types_descriptor_proto_rawDesc = nil + file_types_descriptor_proto_goTypes = nil + file_types_descriptor_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/descriptor.proto b/vendor/github.com/containerd/containerd/api/types/descriptor.proto new file mode 100644 index 000000000..9baadad19 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/descriptor.proto @@ -0,0 +1,33 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +// Descriptor describes a blob in a content store. +// +// This descriptor can be used to reference content from an +// oci descriptor found in a manifest. +// See https://godoc.org/github.com/opencontainers/image-spec/specs-go/v1#Descriptor +message Descriptor { + string media_type = 1; + string digest = 2; + int64 size = 3; + map annotations = 5; +} diff --git a/vendor/github.com/containerd/containerd/api/types/doc.go b/vendor/github.com/containerd/containerd/api/types/doc.go new file mode 100644 index 000000000..475b465ed --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/doc.go @@ -0,0 +1,17 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package types diff --git a/vendor/github.com/containerd/containerd/api/types/event.pb.go b/vendor/github.com/containerd/containerd/api/types/event.pb.go new file mode 100644 index 000000000..a1984674d --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/event.pb.go @@ -0,0 +1,205 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/event.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Envelope struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + Topic string `protobuf:"bytes,3,opt,name=topic,proto3" json:"topic,omitempty"` + Event *anypb.Any `protobuf:"bytes,4,opt,name=event,proto3" json:"event,omitempty"` +} + +func (x *Envelope) Reset() { + *x = Envelope{} + if protoimpl.UnsafeEnabled { + mi := &file_types_event_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Envelope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Envelope) ProtoMessage() {} + +func (x *Envelope) ProtoReflect() protoreflect.Message { + mi := &file_types_event_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Envelope.ProtoReflect.Descriptor instead. +func (*Envelope) Descriptor() ([]byte, []int) { + return file_types_event_proto_rawDescGZIP(), []int{0} +} + +func (x *Envelope) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +func (x *Envelope) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *Envelope) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *Envelope) GetEvent() *anypb.Any { + if x != nil { + return x.Event + } + return nil +} + +var File_types_event_proto protoreflect.FileDescriptor + +var file_types_event_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x15, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x70, 0x61, + 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaa, 0x01, 0x0a, 0x08, 0x45, 0x6e, 0x76, + 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x01, 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, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, + 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, + 0x70, 0x69, 0x63, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x3a, + 0x04, 0x80, 0xb9, 0x1f, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_types_event_proto_rawDescOnce sync.Once + file_types_event_proto_rawDescData = file_types_event_proto_rawDesc +) + +func file_types_event_proto_rawDescGZIP() []byte { + file_types_event_proto_rawDescOnce.Do(func() { + file_types_event_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_event_proto_rawDescData) + }) + return file_types_event_proto_rawDescData +} + +var file_types_event_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_types_event_proto_goTypes = []interface{}{ + (*Envelope)(nil), // 0: containerd.types.Envelope + (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp + (*anypb.Any)(nil), // 2: google.protobuf.Any +} +var file_types_event_proto_depIdxs = []int32{ + 1, // 0: containerd.types.Envelope.timestamp:type_name -> google.protobuf.Timestamp + 2, // 1: containerd.types.Envelope.event:type_name -> google.protobuf.Any + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_types_event_proto_init() } +func file_types_event_proto_init() { + if File_types_event_proto != nil { + return + } + file_types_fieldpath_proto_init() + if !protoimpl.UnsafeEnabled { + file_types_event_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Envelope); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_event_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_event_proto_goTypes, + DependencyIndexes: file_types_event_proto_depIdxs, + MessageInfos: file_types_event_proto_msgTypes, + }.Build() + File_types_event_proto = out.File + file_types_event_proto_rawDesc = nil + file_types_event_proto_goTypes = nil + file_types_event_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/event.proto b/vendor/github.com/containerd/containerd/api/types/event.proto new file mode 100644 index 000000000..0b9c3fb93 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/event.proto @@ -0,0 +1,33 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; +import "types/fieldpath.proto"; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +message Envelope { + option (containerd.types.fieldpath) = true; + google.protobuf.Timestamp timestamp = 1; + string namespace = 2; + string topic = 3; + google.protobuf.Any event = 4; +} diff --git a/vendor/github.com/containerd/containerd/api/types/fieldpath.pb.go b/vendor/github.com/containerd/containerd/api/types/fieldpath.pb.go new file mode 100644 index 000000000..64172a51e --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/fieldpath.pb.go @@ -0,0 +1,142 @@ +// Protocol Buffers for Go with Gadgets +// +// Copyright (c) 2013, The GoGo Authors. All rights reserved. +// http://github.com/gogo/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/fieldpath.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var file_types_fieldpath_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.FileOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 63300, + Name: "containerd.types.fieldpath_all", + Tag: "varint,63300,opt,name=fieldpath_all", + Filename: "types/fieldpath.proto", + }, + { + ExtendedType: (*descriptorpb.MessageOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 64400, + Name: "containerd.types.fieldpath", + Tag: "varint,64400,opt,name=fieldpath", + Filename: "types/fieldpath.proto", + }, +} + +// Extension fields to descriptorpb.FileOptions. +var ( + // optional bool fieldpath_all = 63300; + E_FieldpathAll = &file_types_fieldpath_proto_extTypes[0] +) + +// Extension fields to descriptorpb.MessageOptions. +var ( + // optional bool fieldpath = 64400; + E_Fieldpath = &file_types_fieldpath_proto_extTypes[1] +) + +var File_types_fieldpath_proto protoreflect.FileDescriptor + +var file_types_fieldpath_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x70, 0x61, 0x74, + 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3a, 0x46, 0x0a, 0x0d, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x61, 0x6c, 0x6c, 0x12, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, + 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xc4, 0xee, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x70, 0x61, 0x74, 0x68, 0x41, 0x6c, 0x6c, + 0x88, 0x01, 0x01, 0x3a, 0x42, 0x0a, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x70, 0x61, 0x74, 0x68, + 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x90, 0xf7, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x70, 0x61, 0x74, 0x68, 0x88, 0x01, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var file_types_fieldpath_proto_goTypes = []interface{}{ + (*descriptorpb.FileOptions)(nil), // 0: google.protobuf.FileOptions + (*descriptorpb.MessageOptions)(nil), // 1: google.protobuf.MessageOptions +} +var file_types_fieldpath_proto_depIdxs = []int32{ + 0, // 0: containerd.types.fieldpath_all:extendee -> google.protobuf.FileOptions + 1, // 1: containerd.types.fieldpath:extendee -> google.protobuf.MessageOptions + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 0, // [0:2] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_types_fieldpath_proto_init() } +func file_types_fieldpath_proto_init() { + if File_types_fieldpath_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_fieldpath_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 2, + NumServices: 0, + }, + GoTypes: file_types_fieldpath_proto_goTypes, + DependencyIndexes: file_types_fieldpath_proto_depIdxs, + ExtensionInfos: file_types_fieldpath_proto_extTypes, + }.Build() + File_types_fieldpath_proto = out.File + file_types_fieldpath_proto_rawDesc = nil + file_types_fieldpath_proto_goTypes = nil + file_types_fieldpath_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/fieldpath.proto b/vendor/github.com/containerd/containerd/api/types/fieldpath.proto new file mode 100644 index 000000000..bb5591da5 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/fieldpath.proto @@ -0,0 +1,42 @@ +// Protocol Buffers for Go with Gadgets +// +// Copyright (c) 2013, The GoGo Authors. All rights reserved. +// http://github.com/gogo/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; +package containerd.types; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +extend google.protobuf.FileOptions { + optional bool fieldpath_all = 63300; +} + +extend google.protobuf.MessageOptions { + optional bool fieldpath = 64400; +} diff --git a/vendor/github.com/containerd/containerd/api/types/introspection.pb.go b/vendor/github.com/containerd/containerd/api/types/introspection.pb.go new file mode 100644 index 000000000..b5f804473 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/introspection.pb.go @@ -0,0 +1,373 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/introspection.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RuntimeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RuntimePath string `protobuf:"bytes,1,opt,name=runtime_path,json=runtimePath,proto3" json:"runtime_path,omitempty"` + // Options correspond to CreateTaskRequest.options. + // This is needed to pass the runc binary path, etc. + Options *anypb.Any `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *RuntimeRequest) Reset() { + *x = RuntimeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_types_introspection_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RuntimeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuntimeRequest) ProtoMessage() {} + +func (x *RuntimeRequest) ProtoReflect() protoreflect.Message { + mi := &file_types_introspection_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RuntimeRequest.ProtoReflect.Descriptor instead. +func (*RuntimeRequest) Descriptor() ([]byte, []int) { + return file_types_introspection_proto_rawDescGZIP(), []int{0} +} + +func (x *RuntimeRequest) GetRuntimePath() string { + if x != nil { + return x.RuntimePath + } + return "" +} + +func (x *RuntimeRequest) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +type RuntimeVersion struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"` +} + +func (x *RuntimeVersion) Reset() { + *x = RuntimeVersion{} + if protoimpl.UnsafeEnabled { + mi := &file_types_introspection_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RuntimeVersion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuntimeVersion) ProtoMessage() {} + +func (x *RuntimeVersion) ProtoReflect() protoreflect.Message { + mi := &file_types_introspection_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RuntimeVersion.ProtoReflect.Descriptor instead. +func (*RuntimeVersion) Descriptor() ([]byte, []int) { + return file_types_introspection_proto_rawDescGZIP(), []int{1} +} + +func (x *RuntimeVersion) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *RuntimeVersion) GetRevision() string { + if x != nil { + return x.Revision + } + return "" +} + +type RuntimeInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Version *RuntimeVersion `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + // Options correspond to RuntimeInfoRequest.Options (contains runc binary path, etc.) + Options *anypb.Any `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` + // OCI-compatible runtimes should use https://github.com/opencontainers/runtime-spec/blob/main/features.md + Features *anypb.Any `protobuf:"bytes,4,opt,name=features,proto3" json:"features,omitempty"` + // Annotations of the shim. Irrelevant to features.Annotations. + Annotations map[string]string `protobuf:"bytes,5,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *RuntimeInfo) Reset() { + *x = RuntimeInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_types_introspection_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RuntimeInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuntimeInfo) ProtoMessage() {} + +func (x *RuntimeInfo) ProtoReflect() protoreflect.Message { + mi := &file_types_introspection_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RuntimeInfo.ProtoReflect.Descriptor instead. +func (*RuntimeInfo) Descriptor() ([]byte, []int) { + return file_types_introspection_proto_rawDescGZIP(), []int{2} +} + +func (x *RuntimeInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RuntimeInfo) GetVersion() *RuntimeVersion { + if x != nil { + return x.Version + } + return nil +} + +func (x *RuntimeInfo) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +func (x *RuntimeInfo) GetFeatures() *anypb.Any { + if x != nil { + return x.Features + } + return nil +} + +func (x *RuntimeInfo) GetAnnotations() map[string]string { + if x != nil { + return x.Annotations + } + return nil +} + +var File_types_introspection_proto protoreflect.FileDescriptor + +var file_types_introspection_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x19, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, + 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x63, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, + 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x46, 0x0a, + 0x0e, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xd1, 0x02, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x08, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x50, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, + 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 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, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_types_introspection_proto_rawDescOnce sync.Once + file_types_introspection_proto_rawDescData = file_types_introspection_proto_rawDesc +) + +func file_types_introspection_proto_rawDescGZIP() []byte { + file_types_introspection_proto_rawDescOnce.Do(func() { + file_types_introspection_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_introspection_proto_rawDescData) + }) + return file_types_introspection_proto_rawDescData +} + +var file_types_introspection_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_types_introspection_proto_goTypes = []interface{}{ + (*RuntimeRequest)(nil), // 0: containerd.types.RuntimeRequest + (*RuntimeVersion)(nil), // 1: containerd.types.RuntimeVersion + (*RuntimeInfo)(nil), // 2: containerd.types.RuntimeInfo + nil, // 3: containerd.types.RuntimeInfo.AnnotationsEntry + (*anypb.Any)(nil), // 4: google.protobuf.Any +} +var file_types_introspection_proto_depIdxs = []int32{ + 4, // 0: containerd.types.RuntimeRequest.options:type_name -> google.protobuf.Any + 1, // 1: containerd.types.RuntimeInfo.version:type_name -> containerd.types.RuntimeVersion + 4, // 2: containerd.types.RuntimeInfo.options:type_name -> google.protobuf.Any + 4, // 3: containerd.types.RuntimeInfo.features:type_name -> google.protobuf.Any + 3, // 4: containerd.types.RuntimeInfo.annotations:type_name -> containerd.types.RuntimeInfo.AnnotationsEntry + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_types_introspection_proto_init() } +func file_types_introspection_proto_init() { + if File_types_introspection_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_introspection_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RuntimeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_introspection_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RuntimeVersion); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_introspection_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RuntimeInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_introspection_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_introspection_proto_goTypes, + DependencyIndexes: file_types_introspection_proto_depIdxs, + MessageInfos: file_types_introspection_proto_msgTypes, + }.Build() + File_types_introspection_proto = out.File + file_types_introspection_proto_rawDesc = nil + file_types_introspection_proto_goTypes = nil + file_types_introspection_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/introspection.proto b/vendor/github.com/containerd/containerd/api/types/introspection.proto new file mode 100644 index 000000000..5ce83bfe0 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/introspection.proto @@ -0,0 +1,46 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +import "google/protobuf/any.proto"; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +message RuntimeRequest { + string runtime_path = 1; + // Options correspond to CreateTaskRequest.options. + // This is needed to pass the runc binary path, etc. + google.protobuf.Any options = 2; +} + +message RuntimeVersion { + string version = 1; + string revision = 2; +} + +message RuntimeInfo { + string name = 1; + RuntimeVersion version = 2; + // Options correspond to RuntimeInfoRequest.Options (contains runc binary path, etc.) + google.protobuf.Any options = 3; + // OCI-compatible runtimes should use https://github.com/opencontainers/runtime-spec/blob/main/features.md + google.protobuf.Any features = 4; + // Annotations of the shim. Irrelevant to features.Annotations. + map annotations = 5; +} diff --git a/vendor/github.com/containerd/containerd/api/types/metrics.pb.go b/vendor/github.com/containerd/containerd/api/types/metrics.pb.go new file mode 100644 index 000000000..1cd6a7548 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/metrics.pb.go @@ -0,0 +1,191 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/metrics.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Metric struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + ID string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Data *anypb.Any `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Metric) Reset() { + *x = Metric{} + if protoimpl.UnsafeEnabled { + mi := &file_types_metrics_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Metric) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Metric) ProtoMessage() {} + +func (x *Metric) ProtoReflect() protoreflect.Message { + mi := &file_types_metrics_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Metric.ProtoReflect.Descriptor instead. +func (*Metric) Descriptor() ([]byte, []int) { + return file_types_metrics_proto_rawDescGZIP(), []int{0} +} + +func (x *Metric) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +func (x *Metric) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *Metric) GetData() *anypb.Any { + if x != nil { + return x.Data + } + return nil +} + +var File_types_metrics_proto protoreflect.FileDescriptor + +var file_types_metrics_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x7c, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x38, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 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, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_types_metrics_proto_rawDescOnce sync.Once + file_types_metrics_proto_rawDescData = file_types_metrics_proto_rawDesc +) + +func file_types_metrics_proto_rawDescGZIP() []byte { + file_types_metrics_proto_rawDescOnce.Do(func() { + file_types_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_metrics_proto_rawDescData) + }) + return file_types_metrics_proto_rawDescData +} + +var file_types_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_types_metrics_proto_goTypes = []interface{}{ + (*Metric)(nil), // 0: containerd.types.Metric + (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp + (*anypb.Any)(nil), // 2: google.protobuf.Any +} +var file_types_metrics_proto_depIdxs = []int32{ + 1, // 0: containerd.types.Metric.timestamp:type_name -> google.protobuf.Timestamp + 2, // 1: containerd.types.Metric.data:type_name -> google.protobuf.Any + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_types_metrics_proto_init() } +func file_types_metrics_proto_init() { + if File_types_metrics_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Metric); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_metrics_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_metrics_proto_goTypes, + DependencyIndexes: file_types_metrics_proto_depIdxs, + MessageInfos: file_types_metrics_proto_msgTypes, + }.Build() + File_types_metrics_proto = out.File + file_types_metrics_proto_rawDesc = nil + file_types_metrics_proto_goTypes = nil + file_types_metrics_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/metrics.proto b/vendor/github.com/containerd/containerd/api/types/metrics.proto new file mode 100644 index 000000000..d1a7d49cc --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/metrics.proto @@ -0,0 +1,30 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +message Metric { + google.protobuf.Timestamp timestamp = 1; + string id = 2; + google.protobuf.Any data = 3; +} diff --git a/vendor/github.com/containerd/containerd/api/types/mount.pb.go b/vendor/github.com/containerd/containerd/api/types/mount.pb.go new file mode 100644 index 000000000..5191270fe --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/mount.pb.go @@ -0,0 +1,414 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/mount.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Mount describes mounts for a container. +// +// This type is the lingua franca of ContainerD. All services provide mounts +// to be used with the container at creation time. +// +// The Mount type follows the structure of the mount syscall, including a type, +// source, target and options. +type Mount struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Type defines the nature of the mount. + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // Source specifies the name of the mount. Depending on mount type, this + // may be a volume name or a host path, or even ignored. + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` + // Target path in container + Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` + // Options specifies zero or more fstab style mount options. + Options []string `protobuf:"bytes,4,rep,name=options,proto3" json:"options,omitempty"` +} + +func (x *Mount) Reset() { + *x = Mount{} + if protoimpl.UnsafeEnabled { + mi := &file_types_mount_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Mount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Mount) ProtoMessage() {} + +func (x *Mount) ProtoReflect() protoreflect.Message { + mi := &file_types_mount_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Mount.ProtoReflect.Descriptor instead. +func (*Mount) Descriptor() ([]byte, []int) { + return file_types_mount_proto_rawDescGZIP(), []int{0} +} + +func (x *Mount) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Mount) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *Mount) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *Mount) GetOptions() []string { + if x != nil { + return x.Options + } + return nil +} + +type ActiveMount struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mount *Mount `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + MountedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=mounted_at,json=mountedAt,proto3" json:"mounted_at,omitempty"` + MountPoint string `protobuf:"bytes,3,opt,name=mount_point,json=mountPoint,proto3" json:"mount_point,omitempty"` + Data map[string]string `protobuf:"bytes,4,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ActiveMount) Reset() { + *x = ActiveMount{} + if protoimpl.UnsafeEnabled { + mi := &file_types_mount_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ActiveMount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActiveMount) ProtoMessage() {} + +func (x *ActiveMount) ProtoReflect() protoreflect.Message { + mi := &file_types_mount_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActiveMount.ProtoReflect.Descriptor instead. +func (*ActiveMount) Descriptor() ([]byte, []int) { + return file_types_mount_proto_rawDescGZIP(), []int{1} +} + +func (x *ActiveMount) GetMount() *Mount { + if x != nil { + return x.Mount + } + return nil +} + +func (x *ActiveMount) GetMountedAt() *timestamppb.Timestamp { + if x != nil { + return x.MountedAt + } + return nil +} + +func (x *ActiveMount) GetMountPoint() string { + if x != nil { + return x.MountPoint + } + return "" +} + +func (x *ActiveMount) GetData() map[string]string { + if x != nil { + return x.Data + } + return nil +} + +type ActivationInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Active []*ActiveMount `protobuf:"bytes,2,rep,name=active,proto3" json:"active,omitempty"` + System []*Mount `protobuf:"bytes,3,rep,name=system,proto3" json:"system,omitempty"` + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ActivationInfo) Reset() { + *x = ActivationInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_types_mount_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ActivationInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActivationInfo) ProtoMessage() {} + +func (x *ActivationInfo) ProtoReflect() protoreflect.Message { + mi := &file_types_mount_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActivationInfo.ProtoReflect.Descriptor instead. +func (*ActivationInfo) Descriptor() ([]byte, []int) { + return file_types_mount_proto_rawDescGZIP(), []int{2} +} + +func (x *ActivationInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ActivationInfo) GetActive() []*ActiveMount { + if x != nil { + return x.Active + } + return nil +} + +func (x *ActivationInfo) GetSystem() []*Mount { + if x != nil { + return x.System + } + return nil +} + +func (x *ActivationInfo) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +var File_types_mount_proto protoreflect.FileDescriptor + +var file_types_mount_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x65, 0x0a, 0x05, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8e, 0x02, + 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2d, 0x0a, + 0x05, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x05, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0a, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 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, 0x09, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x44, 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, 0x8d, + 0x02, 0x0a, 0x0e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4d, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x2f, 0x0a, 0x06, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x44, 0x0a, + 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x2e, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 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, 0x42, 0x32, + 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_types_mount_proto_rawDescOnce sync.Once + file_types_mount_proto_rawDescData = file_types_mount_proto_rawDesc +) + +func file_types_mount_proto_rawDescGZIP() []byte { + file_types_mount_proto_rawDescOnce.Do(func() { + file_types_mount_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_mount_proto_rawDescData) + }) + return file_types_mount_proto_rawDescData +} + +var file_types_mount_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_types_mount_proto_goTypes = []interface{}{ + (*Mount)(nil), // 0: containerd.types.Mount + (*ActiveMount)(nil), // 1: containerd.types.ActiveMount + (*ActivationInfo)(nil), // 2: containerd.types.ActivationInfo + nil, // 3: containerd.types.ActiveMount.DataEntry + nil, // 4: containerd.types.ActivationInfo.LabelsEntry + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_types_mount_proto_depIdxs = []int32{ + 0, // 0: containerd.types.ActiveMount.mount:type_name -> containerd.types.Mount + 5, // 1: containerd.types.ActiveMount.mounted_at:type_name -> google.protobuf.Timestamp + 3, // 2: containerd.types.ActiveMount.data:type_name -> containerd.types.ActiveMount.DataEntry + 1, // 3: containerd.types.ActivationInfo.active:type_name -> containerd.types.ActiveMount + 0, // 4: containerd.types.ActivationInfo.system:type_name -> containerd.types.Mount + 4, // 5: containerd.types.ActivationInfo.labels:type_name -> containerd.types.ActivationInfo.LabelsEntry + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_types_mount_proto_init() } +func file_types_mount_proto_init() { + if File_types_mount_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_mount_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Mount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_mount_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ActiveMount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_mount_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ActivationInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_mount_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_mount_proto_goTypes, + DependencyIndexes: file_types_mount_proto_depIdxs, + MessageInfos: file_types_mount_proto_msgTypes, + }.Build() + File_types_mount_proto = out.File + file_types_mount_proto_rawDesc = nil + file_types_mount_proto_goTypes = nil + file_types_mount_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/mount.proto b/vendor/github.com/containerd/containerd/api/types/mount.proto new file mode 100644 index 000000000..05fd4532b --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/mount.proto @@ -0,0 +1,65 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +// Mount describes mounts for a container. +// +// This type is the lingua franca of ContainerD. All services provide mounts +// to be used with the container at creation time. +// +// The Mount type follows the structure of the mount syscall, including a type, +// source, target and options. +message Mount { + // Type defines the nature of the mount. + string type = 1; + + // Source specifies the name of the mount. Depending on mount type, this + // may be a volume name or a host path, or even ignored. + string source = 2; + + // Target path in container + string target = 3; + + // Options specifies zero or more fstab style mount options. + repeated string options = 4; +} + +message ActiveMount { + Mount mount = 1; + + google.protobuf.Timestamp mounted_at = 2; + + string mount_point = 3; + + map data = 4; +} + +message ActivationInfo { + string name = 1; + + repeated ActiveMount active = 2; + + repeated Mount system = 3; + + map labels = 4; +} diff --git a/vendor/github.com/containerd/containerd/api/types/platform.pb.go b/vendor/github.com/containerd/containerd/api/types/platform.pb.go new file mode 100644 index 000000000..947b520db --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/platform.pb.go @@ -0,0 +1,201 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/platform.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Platform follows the structure of the OCI platform specification, from +// descriptors. +type Platform struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OS string `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"` + Architecture string `protobuf:"bytes,2,opt,name=architecture,proto3" json:"architecture,omitempty"` + Variant string `protobuf:"bytes,3,opt,name=variant,proto3" json:"variant,omitempty"` + OSVersion string `protobuf:"bytes,4,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` + OSFeatures []string `protobuf:"bytes,5,rep,name=os_features,json=osFeatures,proto3" json:"os_features,omitempty"` +} + +func (x *Platform) Reset() { + *x = Platform{} + if protoimpl.UnsafeEnabled { + mi := &file_types_platform_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Platform) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Platform) ProtoMessage() {} + +func (x *Platform) ProtoReflect() protoreflect.Message { + mi := &file_types_platform_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Platform.ProtoReflect.Descriptor instead. +func (*Platform) Descriptor() ([]byte, []int) { + return file_types_platform_proto_rawDescGZIP(), []int{0} +} + +func (x *Platform) GetOS() string { + if x != nil { + return x.OS + } + return "" +} + +func (x *Platform) GetArchitecture() string { + if x != nil { + return x.Architecture + } + return "" +} + +func (x *Platform) GetVariant() string { + if x != nil { + return x.Variant + } + return "" +} + +func (x *Platform) GetOsVersion() string { + if x != nil { + return x.OSVersion + } + return "" +} + +func (x *Platform) GetOsFeatures() []string { + if x != nil { + return x.OSFeatures + } + return nil +} + +var File_types_platform_proto protoreflect.FileDescriptor + +var file_types_platform_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x98, 0x01, 0x0a, 0x08, 0x50, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, + 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, + 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x61, 0x72, + 0x69, 0x61, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x61, 0x72, 0x69, + 0x61, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x73, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x73, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_types_platform_proto_rawDescOnce sync.Once + file_types_platform_proto_rawDescData = file_types_platform_proto_rawDesc +) + +func file_types_platform_proto_rawDescGZIP() []byte { + file_types_platform_proto_rawDescOnce.Do(func() { + file_types_platform_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_platform_proto_rawDescData) + }) + return file_types_platform_proto_rawDescData +} + +var file_types_platform_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_types_platform_proto_goTypes = []interface{}{ + (*Platform)(nil), // 0: containerd.types.Platform +} +var file_types_platform_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_types_platform_proto_init() } +func file_types_platform_proto_init() { + if File_types_platform_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_platform_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Platform); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_platform_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_platform_proto_goTypes, + DependencyIndexes: file_types_platform_proto_depIdxs, + MessageInfos: file_types_platform_proto_msgTypes, + }.Build() + File_types_platform_proto = out.File + file_types_platform_proto_rawDesc = nil + file_types_platform_proto_goTypes = nil + file_types_platform_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/platform.proto b/vendor/github.com/containerd/containerd/api/types/platform.proto new file mode 100644 index 000000000..33ed85301 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/platform.proto @@ -0,0 +1,31 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +// Platform follows the structure of the OCI platform specification, from +// descriptors. +message Platform { + string os = 1; + string architecture = 2; + string variant = 3; + string os_version = 4; + repeated string os_features = 5; +} diff --git a/vendor/github.com/containerd/containerd/api/types/platform_helpers.go b/vendor/github.com/containerd/containerd/api/types/platform_helpers.go new file mode 100644 index 000000000..ba1fec577 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/platform_helpers.go @@ -0,0 +1,51 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package types + +import oci "github.com/opencontainers/image-spec/specs-go/v1" + +// OCIPlatformToProto converts from a slice of OCI [specs.Platform] to a +// slice of the protobuf definition [Platform]. +func OCIPlatformToProto(platforms []oci.Platform) []*Platform { + ap := make([]*Platform, len(platforms)) + for i := range platforms { + ap[i] = &Platform{ + OS: platforms[i].OS, + OSVersion: platforms[i].OSVersion, + Architecture: platforms[i].Architecture, + Variant: platforms[i].Variant, + OSFeatures: platforms[i].OSFeatures, + } + } + return ap +} + +// OCIPlatformFromProto converts a slice of the protobuf definition [Platform] +// to a slice of OCI [specs.Platform]. +func OCIPlatformFromProto(platforms []*Platform) []oci.Platform { + op := make([]oci.Platform, len(platforms)) + for i := range platforms { + op[i] = oci.Platform{ + OS: platforms[i].OS, + OSVersion: platforms[i].OSVersion, + Architecture: platforms[i].Architecture, + Variant: platforms[i].Variant, + OSFeatures: platforms[i].OSFeatures, + } + } + return op +} diff --git a/vendor/github.com/containerd/containerd/api/types/sandbox.pb.go b/vendor/github.com/containerd/containerd/api/types/sandbox.pb.go new file mode 100644 index 000000000..eb84c7be6 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/sandbox.pb.go @@ -0,0 +1,354 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/sandbox.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Sandbox represents a sandbox metadata object that keeps all info required by controller to +// work with a particular instance. +type Sandbox struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // SandboxID is a unique instance identifier within namespace + SandboxID string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Runtime specifies which runtime to use for executing this container. + Runtime *Sandbox_Runtime `protobuf:"bytes,2,opt,name=runtime,proto3" json:"runtime,omitempty"` + // Spec is sandbox configuration (kin of OCI runtime spec), spec's data will be written to a config.json file in the + // bundle directory (similary to OCI spec). + Spec *anypb.Any `protobuf:"bytes,3,opt,name=spec,proto3" json:"spec,omitempty"` + // Labels provides an area to include arbitrary data on containers. + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // CreatedAt is the time the container was first created. + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + // UpdatedAt is the last time the container was mutated. + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + // Extensions allow clients to provide optional blobs that can be handled by runtime. + Extensions map[string]*anypb.Any `protobuf:"bytes,7,rep,name=extensions,proto3" json:"extensions,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Sandboxer is the name of the sandbox controller who manages the sandbox. + Sandboxer string `protobuf:"bytes,10,opt,name=sandboxer,proto3" json:"sandboxer,omitempty"` +} + +func (x *Sandbox) Reset() { + *x = Sandbox{} + if protoimpl.UnsafeEnabled { + mi := &file_types_sandbox_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Sandbox) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Sandbox) ProtoMessage() {} + +func (x *Sandbox) ProtoReflect() protoreflect.Message { + mi := &file_types_sandbox_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Sandbox.ProtoReflect.Descriptor instead. +func (*Sandbox) Descriptor() ([]byte, []int) { + return file_types_sandbox_proto_rawDescGZIP(), []int{0} +} + +func (x *Sandbox) GetSandboxID() string { + if x != nil { + return x.SandboxID + } + return "" +} + +func (x *Sandbox) GetRuntime() *Sandbox_Runtime { + if x != nil { + return x.Runtime + } + return nil +} + +func (x *Sandbox) GetSpec() *anypb.Any { + if x != nil { + return x.Spec + } + return nil +} + +func (x *Sandbox) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *Sandbox) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Sandbox) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *Sandbox) GetExtensions() map[string]*anypb.Any { + if x != nil { + return x.Extensions + } + return nil +} + +func (x *Sandbox) GetSandboxer() string { + if x != nil { + return x.Sandboxer + } + return "" +} + +type Sandbox_Runtime struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is the name of the runtime. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Options specify additional runtime initialization options for the shim (this data will be available in StartShim). + // Typically this data expected to be runtime shim implementation specific. + Options *anypb.Any `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *Sandbox_Runtime) Reset() { + *x = Sandbox_Runtime{} + if protoimpl.UnsafeEnabled { + mi := &file_types_sandbox_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Sandbox_Runtime) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Sandbox_Runtime) ProtoMessage() {} + +func (x *Sandbox_Runtime) ProtoReflect() protoreflect.Message { + mi := &file_types_sandbox_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Sandbox_Runtime.ProtoReflect.Descriptor instead. +func (*Sandbox_Runtime) Descriptor() ([]byte, []int) { + return file_types_sandbox_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Sandbox_Runtime) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Sandbox_Runtime) GetOptions() *anypb.Any { + if x != nil { + return x.Options + } + return nil +} + +var File_types_sandbox_proto protoreflect.FileDescriptor + +var file_types_sandbox_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x8c, 0x05, 0x0a, 0x07, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 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, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x3b, + 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x2e, 0x52, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x52, 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x73, + 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, + 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x3d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x05, 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, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, + 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 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, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x49, 0x0a, 0x0a, 0x65, 0x78, + 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, + 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, + 0x78, 0x65, 0x72, 0x1a, 0x4d, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 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, 0x53, 0x0a, + 0x0f, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_types_sandbox_proto_rawDescOnce sync.Once + file_types_sandbox_proto_rawDescData = file_types_sandbox_proto_rawDesc +) + +func file_types_sandbox_proto_rawDescGZIP() []byte { + file_types_sandbox_proto_rawDescOnce.Do(func() { + file_types_sandbox_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_sandbox_proto_rawDescData) + }) + return file_types_sandbox_proto_rawDescData +} + +var file_types_sandbox_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_types_sandbox_proto_goTypes = []interface{}{ + (*Sandbox)(nil), // 0: containerd.types.Sandbox + (*Sandbox_Runtime)(nil), // 1: containerd.types.Sandbox.Runtime + nil, // 2: containerd.types.Sandbox.LabelsEntry + nil, // 3: containerd.types.Sandbox.ExtensionsEntry + (*anypb.Any)(nil), // 4: google.protobuf.Any + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_types_sandbox_proto_depIdxs = []int32{ + 1, // 0: containerd.types.Sandbox.runtime:type_name -> containerd.types.Sandbox.Runtime + 4, // 1: containerd.types.Sandbox.spec:type_name -> google.protobuf.Any + 2, // 2: containerd.types.Sandbox.labels:type_name -> containerd.types.Sandbox.LabelsEntry + 5, // 3: containerd.types.Sandbox.created_at:type_name -> google.protobuf.Timestamp + 5, // 4: containerd.types.Sandbox.updated_at:type_name -> google.protobuf.Timestamp + 3, // 5: containerd.types.Sandbox.extensions:type_name -> containerd.types.Sandbox.ExtensionsEntry + 4, // 6: containerd.types.Sandbox.Runtime.options:type_name -> google.protobuf.Any + 4, // 7: containerd.types.Sandbox.ExtensionsEntry.value:type_name -> google.protobuf.Any + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name +} + +func init() { file_types_sandbox_proto_init() } +func file_types_sandbox_proto_init() { + if File_types_sandbox_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_sandbox_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Sandbox); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_sandbox_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Sandbox_Runtime); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_sandbox_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_sandbox_proto_goTypes, + DependencyIndexes: file_types_sandbox_proto_depIdxs, + MessageInfos: file_types_sandbox_proto_msgTypes, + }.Build() + File_types_sandbox_proto = out.File + file_types_sandbox_proto_rawDesc = nil + file_types_sandbox_proto_goTypes = nil + file_types_sandbox_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/sandbox.proto b/vendor/github.com/containerd/containerd/api/types/sandbox.proto new file mode 100644 index 000000000..023644192 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/sandbox.proto @@ -0,0 +1,53 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.types; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/containerd/containerd/api/types;types"; + +// Sandbox represents a sandbox metadata object that keeps all info required by controller to +// work with a particular instance. +message Sandbox { + // SandboxID is a unique instance identifier within namespace + string sandbox_id = 1; + message Runtime { + // Name is the name of the runtime. + string name = 1; + // Options specify additional runtime initialization options for the shim (this data will be available in StartShim). + // Typically this data expected to be runtime shim implementation specific. + google.protobuf.Any options = 2; + } + // Runtime specifies which runtime to use for executing this container. + Runtime runtime = 2; + // Spec is sandbox configuration (kin of OCI runtime spec), spec's data will be written to a config.json file in the + // bundle directory (similary to OCI spec). + google.protobuf.Any spec = 3; + // Labels provides an area to include arbitrary data on containers. + map labels = 4; + // CreatedAt is the time the container was first created. + google.protobuf.Timestamp created_at = 5; + // UpdatedAt is the last time the container was mutated. + google.protobuf.Timestamp updated_at = 6; + // Extensions allow clients to provide optional blobs that can be handled by runtime. + map extensions = 7; + // Sandboxer is the name of the sandbox controller who manages the sandbox. + string sandboxer = 10; +} diff --git a/vendor/github.com/containerd/containerd/api/types/task/doc.go b/vendor/github.com/containerd/containerd/api/types/task/doc.go new file mode 100644 index 000000000..e10c7a469 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/task/doc.go @@ -0,0 +1,18 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package task defines the task service. +package task diff --git a/vendor/github.com/containerd/containerd/api/types/task/task.pb.go b/vendor/github.com/containerd/containerd/api/types/task/task.pb.go new file mode 100644 index 000000000..c81d4b7d3 --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/task/task.pb.go @@ -0,0 +1,404 @@ +// +//Copyright The containerd Authors. +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: types/task/task.proto + +package task + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Status int32 + +const ( + Status_UNKNOWN Status = 0 + Status_CREATED Status = 1 + Status_RUNNING Status = 2 + Status_STOPPED Status = 3 + Status_PAUSED Status = 4 + Status_PAUSING Status = 5 +) + +// Enum value maps for Status. +var ( + Status_name = map[int32]string{ + 0: "UNKNOWN", + 1: "CREATED", + 2: "RUNNING", + 3: "STOPPED", + 4: "PAUSED", + 5: "PAUSING", + } + Status_value = map[string]int32{ + "UNKNOWN": 0, + "CREATED": 1, + "RUNNING": 2, + "STOPPED": 3, + "PAUSED": 4, + "PAUSING": 5, + } +) + +func (x Status) Enum() *Status { + p := new(Status) + *p = x + return p +} + +func (x Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Status) Descriptor() protoreflect.EnumDescriptor { + return file_types_task_task_proto_enumTypes[0].Descriptor() +} + +func (Status) Type() protoreflect.EnumType { + return &file_types_task_task_proto_enumTypes[0] +} + +func (x Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Status.Descriptor instead. +func (Status) EnumDescriptor() ([]byte, []int) { + return file_types_task_task_proto_rawDescGZIP(), []int{0} +} + +type Process struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ContainerID string `protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + ID string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Pid uint32 `protobuf:"varint,3,opt,name=pid,proto3" json:"pid,omitempty"` + Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=containerd.v1.types.Status" json:"status,omitempty"` + Stdin string `protobuf:"bytes,5,opt,name=stdin,proto3" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,6,opt,name=stdout,proto3" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,7,opt,name=stderr,proto3" json:"stderr,omitempty"` + Terminal bool `protobuf:"varint,8,opt,name=terminal,proto3" json:"terminal,omitempty"` + ExitStatus uint32 `protobuf:"varint,9,opt,name=exit_status,json=exitStatus,proto3" json:"exit_status,omitempty"` + ExitedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=exited_at,json=exitedAt,proto3" json:"exited_at,omitempty"` +} + +func (x *Process) Reset() { + *x = Process{} + if protoimpl.UnsafeEnabled { + mi := &file_types_task_task_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Process) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Process) ProtoMessage() {} + +func (x *Process) ProtoReflect() protoreflect.Message { + mi := &file_types_task_task_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Process.ProtoReflect.Descriptor instead. +func (*Process) Descriptor() ([]byte, []int) { + return file_types_task_task_proto_rawDescGZIP(), []int{0} +} + +func (x *Process) GetContainerID() string { + if x != nil { + return x.ContainerID + } + return "" +} + +func (x *Process) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *Process) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *Process) GetStatus() Status { + if x != nil { + return x.Status + } + return Status_UNKNOWN +} + +func (x *Process) GetStdin() string { + if x != nil { + return x.Stdin + } + return "" +} + +func (x *Process) GetStdout() string { + if x != nil { + return x.Stdout + } + return "" +} + +func (x *Process) GetStderr() string { + if x != nil { + return x.Stderr + } + return "" +} + +func (x *Process) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *Process) GetExitStatus() uint32 { + if x != nil { + return x.ExitStatus + } + return 0 +} + +func (x *Process) GetExitedAt() *timestamppb.Timestamp { + if x != nil { + return x.ExitedAt + } + return nil +} + +type ProcessInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // PID is the process ID. + Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` + // Info contains additional process information. + // + // Info varies by platform. + Info *anypb.Any `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` +} + +func (x *ProcessInfo) Reset() { + *x = ProcessInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_types_task_task_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProcessInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessInfo) ProtoMessage() {} + +func (x *ProcessInfo) ProtoReflect() protoreflect.Message { + mi := &file_types_task_task_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead. +func (*ProcessInfo) Descriptor() ([]byte, []int) { + return file_types_task_task_proto_rawDescGZIP(), []int{1} +} + +func (x *ProcessInfo) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *ProcessInfo) GetInfo() *anypb.Any { + if x != nil { + return x.Info + } + return nil +} + +var File_types_task_task_proto protoreflect.FileDescriptor + +var file_types_task_task_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x19, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x02, 0x0a, 0x07, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, + 0x74, 0x64, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x64, 0x65, 0x72, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, + 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, + 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, 0x08, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x49, 0x0a, 0x0b, 0x50, 0x72, + 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x04, 0x69, + 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, + 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x2a, 0x55, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, + 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, + 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, + 0x44, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x41, 0x55, 0x53, 0x45, 0x44, 0x10, 0x04, 0x12, + 0x0b, 0x0a, 0x07, 0x50, 0x41, 0x55, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x42, 0x31, 0x5a, 0x2f, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_types_task_task_proto_rawDescOnce sync.Once + file_types_task_task_proto_rawDescData = file_types_task_task_proto_rawDesc +) + +func file_types_task_task_proto_rawDescGZIP() []byte { + file_types_task_task_proto_rawDescOnce.Do(func() { + file_types_task_task_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_task_task_proto_rawDescData) + }) + return file_types_task_task_proto_rawDescData +} + +var file_types_task_task_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_types_task_task_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_types_task_task_proto_goTypes = []interface{}{ + (Status)(0), // 0: containerd.v1.types.Status + (*Process)(nil), // 1: containerd.v1.types.Process + (*ProcessInfo)(nil), // 2: containerd.v1.types.ProcessInfo + (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp + (*anypb.Any)(nil), // 4: google.protobuf.Any +} +var file_types_task_task_proto_depIdxs = []int32{ + 0, // 0: containerd.v1.types.Process.status:type_name -> containerd.v1.types.Status + 3, // 1: containerd.v1.types.Process.exited_at:type_name -> google.protobuf.Timestamp + 4, // 2: containerd.v1.types.ProcessInfo.info:type_name -> google.protobuf.Any + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_types_task_task_proto_init() } +func file_types_task_task_proto_init() { + if File_types_task_task_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_types_task_task_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Process); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_types_task_task_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProcessInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_types_task_task_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_task_task_proto_goTypes, + DependencyIndexes: file_types_task_task_proto_depIdxs, + EnumInfos: file_types_task_task_proto_enumTypes, + MessageInfos: file_types_task_task_proto_msgTypes, + }.Build() + File_types_task_task_proto = out.File + file_types_task_task_proto_rawDesc = nil + file_types_task_task_proto_goTypes = nil + file_types_task_task_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/containerd/api/types/task/task.proto b/vendor/github.com/containerd/containerd/api/types/task/task.proto new file mode 100644 index 000000000..3a06236fa --- /dev/null +++ b/vendor/github.com/containerd/containerd/api/types/task/task.proto @@ -0,0 +1,55 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +syntax = "proto3"; + +package containerd.v1.types; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/containerd/containerd/api/types/task"; + +enum Status { + UNKNOWN = 0; + CREATED = 1; + RUNNING = 2; + STOPPED = 3; + PAUSED = 4; + PAUSING = 5; +} + +message Process { + string container_id = 1; + string id = 2; + uint32 pid = 3; + Status status = 4; + string stdin = 5; + string stdout = 6; + string stderr = 7; + bool terminal = 8; + uint32 exit_status = 9; + google.protobuf.Timestamp exited_at = 10; +} + +message ProcessInfo { + // PID is the process ID. + uint32 pid = 1; + // Info contains additional process information. + // + // Info varies by platform. + google.protobuf.Any info = 2; +} diff --git a/vendor/github.com/containerd/log/.golangci.yml b/vendor/github.com/containerd/log/.golangci.yml new file mode 100644 index 000000000..a695775df --- /dev/null +++ b/vendor/github.com/containerd/log/.golangci.yml @@ -0,0 +1,30 @@ +linters: + enable: + - exportloopref # Checks for pointers to enclosing loop variables + - gofmt + - goimports + - gosec + - ineffassign + - misspell + - nolintlint + - revive + - staticcheck + - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17 + - unconvert + - unused + - vet + - dupword # Checks for duplicate words in the source code + disable: + - errcheck + +run: + timeout: 5m + skip-dirs: + - api + - cluster + - design + - docs + - docs/man + - releases + - reports + - test # e2e scripts diff --git a/vendor/github.com/containerd/log/LICENSE b/vendor/github.com/containerd/log/LICENSE new file mode 100644 index 000000000..584149b6e --- /dev/null +++ b/vendor/github.com/containerd/log/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright The containerd Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containerd/log/README.md b/vendor/github.com/containerd/log/README.md new file mode 100644 index 000000000..00e084988 --- /dev/null +++ b/vendor/github.com/containerd/log/README.md @@ -0,0 +1,17 @@ +# log + +A Go package providing a common logging interface across containerd repositories and a way for clients to use and configure logging in containerd packages. + +This package is not intended to be used as a standalone logging package outside of the containerd ecosystem and is intended as an interface wrapper around a logging implementation. +In the future this package may be replaced with a common go logging interface. + +## Project details + +**log** is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd sub-project, you will find the: + * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), + * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), + * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) + +information in our [`containerd/project`](https://github.com/containerd/project) repository. + diff --git a/vendor/github.com/containerd/log/context.go b/vendor/github.com/containerd/log/context.go new file mode 100644 index 000000000..20153066f --- /dev/null +++ b/vendor/github.com/containerd/log/context.go @@ -0,0 +1,182 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package log provides types and functions related to logging, passing +// loggers through a context, and attaching context to the logger. +// +// # Transitional types +// +// This package contains various types that are aliases for types in [logrus]. +// These aliases are intended for transitioning away from hard-coding logrus +// as logging implementation. Consumers of this package are encouraged to use +// the type-aliases from this package instead of directly using their logrus +// equivalent. +// +// The intent is to replace these aliases with locally defined types and +// interfaces once all consumers are no longer directly importing logrus +// types. +// +// IMPORTANT: due to the transitional purpose of this package, it is not +// guaranteed for the full logrus API to be provided in the future. As +// outlined, these aliases are provided as a step to transition away from +// a specific implementation which, as a result, exposes the full logrus API. +// While no decisions have been made on the ultimate design and interface +// provided by this package, we do not expect carrying "less common" features. +package log + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" +) + +// G is a shorthand for [GetLogger]. +// +// We may want to define this locally to a package to get package tagged log +// messages. +var G = GetLogger + +// L is an alias for the standard logger. +var L = &Entry{ + Logger: logrus.StandardLogger(), + // Default is three fields plus a little extra room. + Data: make(Fields, 6), +} + +type loggerKey struct{} + +// Fields type to pass to "WithFields". +type Fields = map[string]any + +// Entry is a logging entry. It contains all the fields passed with +// [Entry.WithFields]. It's finally logged when Trace, Debug, Info, Warn, +// Error, Fatal or Panic is called on it. These objects can be reused and +// passed around as much as you wish to avoid field duplication. +// +// Entry is a transitional type, and currently an alias for [logrus.Entry]. +type Entry = logrus.Entry + +// RFC3339NanoFixed is [time.RFC3339Nano] with nanoseconds padded using +// zeros to ensure the formatted time is always the same number of +// characters. +const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" + +// Level is a logging level. +type Level = logrus.Level + +// Supported log levels. +const ( + // TraceLevel level. Designates finer-grained informational events + // than [DebugLevel]. + TraceLevel Level = logrus.TraceLevel + + // DebugLevel level. Usually only enabled when debugging. Very verbose + // logging. + DebugLevel Level = logrus.DebugLevel + + // InfoLevel level. General operational entries about what's going on + // inside the application. + InfoLevel Level = logrus.InfoLevel + + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel Level = logrus.WarnLevel + + // ErrorLevel level. Logs errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel Level = logrus.ErrorLevel + + // FatalLevel level. Logs and then calls "logger.Exit(1)". It exits + // even if the logging level is set to Panic. + FatalLevel Level = logrus.FatalLevel + + // PanicLevel level. This is the highest level of severity. Logs and + // then calls panic with the message passed to Debug, Info, ... + PanicLevel Level = logrus.PanicLevel +) + +// SetLevel sets log level globally. It returns an error if the given +// level is not supported. +// +// level can be one of: +// +// - "trace" ([TraceLevel]) +// - "debug" ([DebugLevel]) +// - "info" ([InfoLevel]) +// - "warn" ([WarnLevel]) +// - "error" ([ErrorLevel]) +// - "fatal" ([FatalLevel]) +// - "panic" ([PanicLevel]) +func SetLevel(level string) error { + lvl, err := logrus.ParseLevel(level) + if err != nil { + return err + } + + L.Logger.SetLevel(lvl) + return nil +} + +// GetLevel returns the current log level. +func GetLevel() Level { + return L.Logger.GetLevel() +} + +// OutputFormat specifies a log output format. +type OutputFormat string + +// Supported log output formats. +const ( + // TextFormat represents the text logging format. + TextFormat OutputFormat = "text" + + // JSONFormat represents the JSON logging format. + JSONFormat OutputFormat = "json" +) + +// SetFormat sets the log output format ([TextFormat] or [JSONFormat]). +func SetFormat(format OutputFormat) error { + switch format { + case TextFormat: + L.Logger.SetFormatter(&logrus.TextFormatter{ + TimestampFormat: RFC3339NanoFixed, + FullTimestamp: true, + }) + return nil + case JSONFormat: + L.Logger.SetFormatter(&logrus.JSONFormatter{ + TimestampFormat: RFC3339NanoFixed, + }) + return nil + default: + return fmt.Errorf("unknown log format: %s", format) + } +} + +// WithLogger returns a new context with the provided logger. Use in +// combination with logger.WithField(s) for great effect. +func WithLogger(ctx context.Context, logger *Entry) context.Context { + return context.WithValue(ctx, loggerKey{}, logger.WithContext(ctx)) +} + +// GetLogger retrieves the current logger from the context. If no logger is +// available, the default logger is returned. +func GetLogger(ctx context.Context) *Entry { + if logger := ctx.Value(loggerKey{}); logger != nil { + return logger.(*Entry) + } + return L.WithContext(ctx) +} diff --git a/vendor/github.com/containerd/ttrpc/.gitattributes b/vendor/github.com/containerd/ttrpc/.gitattributes new file mode 100644 index 000000000..d207b1802 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf diff --git a/vendor/github.com/containerd/ttrpc/.gitignore b/vendor/github.com/containerd/ttrpc/.gitignore new file mode 100644 index 000000000..88ceb2764 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/.gitignore @@ -0,0 +1,13 @@ +# Binaries for programs and plugins +/bin/ +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +coverage.txt diff --git a/vendor/github.com/containerd/ttrpc/.golangci.yml b/vendor/github.com/containerd/ttrpc/.golangci.yml new file mode 100644 index 000000000..ef1a7d963 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/.golangci.yml @@ -0,0 +1,56 @@ +version: "2" +linters: + enable: + - misspell + - revive + - unconvert + disable: + - errcheck + settings: + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + - name: var-naming + arguments: + - - UID + - GID + - [] + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - example +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - example diff --git a/vendor/github.com/containerd/ttrpc/LICENSE b/vendor/github.com/containerd/ttrpc/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containerd/ttrpc/Makefile b/vendor/github.com/containerd/ttrpc/Makefile new file mode 100644 index 000000000..c3a497dca --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/Makefile @@ -0,0 +1,180 @@ +# Copyright The containerd Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Go command to use for build +GO ?= go +INSTALL ?= install + +# Root directory of the project (absolute path). +ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +WHALE = "🇩" +ONI = "👹" + +# Project binaries. +COMMANDS=protoc-gen-go-ttrpc protoc-gen-gogottrpc + +ifdef BUILDTAGS + GO_BUILDTAGS = ${BUILDTAGS} +endif +GO_BUILDTAGS ?= +GO_TAGS=$(if $(GO_BUILDTAGS),-tags "$(strip $(GO_BUILDTAGS))",) + +# Project packages. +PACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /example) +TESTPACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /cmd | grep -v /integration | grep -v /example) +BINPACKAGES=$(addprefix ./cmd/,$(COMMANDS)) + +#Replaces ":" (*nix), ";" (windows) with newline for easy parsing +GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n") + +TESTFLAGS_RACE= +GO_BUILD_FLAGS= +# See Golang issue re: '-trimpath': https://github.com/golang/go/issues/13809 +GO_GCFLAGS=$(shell \ + set -- ${GOPATHS}; \ + echo "-gcflags=-trimpath=$${1}/src"; \ + ) + +BINARIES=$(addprefix bin/,$(COMMANDS)) + +# Flags passed to `go test` +TESTFLAGS ?= $(TESTFLAGS_RACE) $(EXTRA_TESTFLAGS) +TESTFLAGS_PARALLEL ?= 8 + +# Use this to replace `go test` with, for instance, `gotestsum` +GOTEST ?= $(GO) test + +.PHONY: clean all AUTHORS build binaries test integration generate protos check-protos coverage ci check help install vendor install-protobuf install-protobuild +.DEFAULT: default + +# Forcibly set the default goal to all, in case an include above brought in a rule definition. +.DEFAULT_GOAL := all + +all: binaries + +check: proto-fmt ## run all linters + @echo "$(WHALE) $@" + GOGC=75 golangci-lint run + +ci: check binaries check-protos coverage # coverage-integration ## to be used by the CI + +AUTHORS: .mailmap .git/HEAD + git log --format='%aN <%aE>' | sort -fu > $@ + +generate: protos + @echo "$(WHALE) $@" + @PATH="${ROOTDIR}/bin:${PATH}" $(GO) generate -x ${PACKAGES} + +protos: bin/protoc-gen-gogottrpc bin/protoc-gen-go-ttrpc ## generate protobuf + @echo "$(WHALE) $@" + @(PATH="${ROOTDIR}/bin:${PATH}" protobuild --quiet ${PACKAGES}) + +check-protos: protos ## check if protobufs needs to be generated again + @echo "$(WHALE) $@" + @test -z "$$(git status --short | grep ".pb.go" | tee /dev/stderr)" || \ + ((git diff | cat) && \ + (echo "$(ONI) please run 'make protos' when making changes to proto files" && false)) + +check-api-descriptors: protos ## check that protobuf changes aren't present. + @echo "$(WHALE) $@" + @test -z "$$(git status --short | grep ".pb.txt" | tee /dev/stderr)" || \ + ((git diff $$(find . -name '*.pb.txt') | cat) && \ + (echo "$(ONI) please run 'make protos' when making changes to proto files and check-in the generated descriptor file changes" && false)) + +proto-fmt: ## check format of proto files + @echo "$(WHALE) $@" + @test -z "$$(find . -name '*.proto' -type f -exec grep -Hn -e "^ " {} \; | tee /dev/stderr)" || \ + (echo "$(ONI) please indent proto files with tabs only" && false) + @test -z "$$(find . -name '*.proto' -type f -exec grep -Hn "Meta meta = " {} \; | grep -v '(gogoproto.nullable) = false' | tee /dev/stderr)" || \ + (echo "$(ONI) meta fields in proto files must have option (gogoproto.nullable) = false" && false) + +build: ## build the go packages + @echo "$(WHALE) $@" + @$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} ${PACKAGES} + +test: ## run tests, except integration tests and tests that require root + @echo "$(WHALE) $@" + @$(GOTEST) ${TESTFLAGS} ${TESTPACKAGES} + +integration: ## run integration tests + @echo "$(WHALE) $@" + @cd "${ROOTDIR}/integration" && $(GOTEST) -v ${TESTFLAGS} -parallel ${TESTFLAGS_PARALLEL} . + +benchmark: ## run benchmarks tests + @echo "$(WHALE) $@" + @$(GO) test ${TESTFLAGS} -bench . -run Benchmark + +FORCE: + +define BUILD_BINARY +@echo "$(WHALE) $@" +@$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@ ${GO_TAGS} ./$< +endef + +# Build a binary from a cmd. +bin/%: cmd/% FORCE + $(call BUILD_BINARY) + +binaries: $(BINARIES) ## build binaries + @echo "$(WHALE) $@" + +clean: ## clean up binaries + @echo "$(WHALE) $@" + @rm -f $(BINARIES) + +install: ## install binaries + @echo "$(WHALE) $@ $(BINPACKAGES)" + @$(GO) install $(BINPACKAGES) + +install-protobuf: + @echo "$(WHALE) $@" + @script/install-protobuf + +install-protobuild: + @echo "$(WHALE) $@" + @$(GO) install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 + @$(GO) install github.com/containerd/protobuild@14832ccc41429f5c4f81028e5af08aa233a219cf + +coverage: ## generate coverprofiles from the unit tests, except tests that require root + @echo "$(WHALE) $@" + @rm -f coverage.txt + @$(GO) test ${TESTFLAGS} ${TESTPACKAGES} 2> /dev/null + @( for pkg in ${PACKAGES}; do \ + $(GO) test ${TESTFLAGS} \ + -cover \ + -coverprofile=profile.out \ + -covermode=atomic $$pkg || exit; \ + if [ -f profile.out ]; then \ + cat profile.out >> coverage.txt; \ + rm profile.out; \ + fi; \ + done ) + +vendor: ## ensure all the go.mod/go.sum files are up-to-date + @echo "$(WHALE) $@" + @$(GO) mod tidy + @$(GO) mod verify + +verify-vendor: ## verify if all the go.mod/go.sum files are up-to-date + @echo "$(WHALE) $@" + @$(GO) mod tidy + @$(GO) mod verify + @test -z "$$(git status --short | grep "go.sum" | tee /dev/stderr)" || \ + ((git diff | cat) && \ + (echo "$(ONI) make sure to checkin changes after go mod tidy" && false)) + +help: ## this help + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort diff --git a/vendor/github.com/containerd/ttrpc/PROTOCOL.md b/vendor/github.com/containerd/ttrpc/PROTOCOL.md new file mode 100644 index 000000000..12b43f6bd --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/PROTOCOL.md @@ -0,0 +1,240 @@ +# Protocol Specification + +The ttrpc protocol is client/server protocol to support multiple request streams +over a single connection with lightweight framing. The client represents the +process which initiated the underlying connection and the server is the process +which accepted the connection. The protocol is currently defined as +asymmetrical, with clients sending requests and servers sending responses. Both +clients and servers are able to send stream data. The roles are also used in +determining the stream identifiers, with client initiated streams using odd +number identifiers and server initiated using even number. The protocol may be +extended in the future to support server initiated streams, that is not +supported in the latest version. + +## Purpose + +The ttrpc protocol is designed to be lightweight and optimized for low latency +and reliable connections between processes on the same host. The protocol does +not include features for handling unreliable connections such as handshakes, +resets, pings, or flow control. The protocol is designed to make low-overhead +implementations as simple as possible. It is not intended as a suitable +replacement for HTTP2/3 over the network. + +## Message Frame + +Each Message Frame consists of a 10-byte message header followed +by message data. The data length and stream ID are both big-endian +4-byte unsigned integers. The message type is an unsigned 1-byte +integer. The flags are also an unsigned 1-byte integer and +use is defined by the message type. + + +---------------------------------------------------------------+ + | Data Length (32) | + +---------------------------------------------------------------+ + | Stream ID (32) | + +---------------+-----------------------------------------------+ + | Msg Type (8) | + +---------------+ + | Flags (8) | + +---------------+-----------------------------------------------+ + | Data (*) | + +---------------------------------------------------------------+ + +The Data Length field represents the number of bytes in the Data field. The +total frame size will always be Data Length + 10 bytes. The maximum data length +is 4MB and any larger size should be rejected. Due to the maximum data size +being less than 16MB, the first frame byte should always be zero. This first +byte should be considered reserved for future use. + +The Stream ID must be odd for client initiated streams and even for server +initiated streams. Server initiated streams are not currently supported. + +## Mesage Types + +| Message Type | Name | Description | +|--------------|----------|----------------------------------| +| 0x01 | Request | Initiates stream | +| 0x02 | Response | Final stream data and terminates | +| 0x03 | Data | Stream data | + +### Request + +The request message is used to initiate stream and send along request data for +properly routing and handling the stream. The stream may indicate unary without +any inbound or outbound stream data with only a response is expected on the +stream. The request may also indicate the stream is still open for more data and +no response is expected until data is finished. If the remote indicates the +stream is closed, the request may be considered non-unary but without anymore +stream data sent. In the case of `remote closed`, the remote still expects to +receive a response or stream data. For compatibility with non streaming clients, +a request with empty flags indicates a unary request. + +#### Request Flags + +| Flag | Name | Description | +|------|-----------------|--------------------------------------------------| +| 0x01 | `remote closed` | Non-unary, but no more data expected from remote | +| 0x02 | `remote open` | Non-unary, remote is still sending data | + +### Response + +The response message is used to end a stream with data, an empty response, or +an error. A response message is the only expected message after a unary request. +A non-unary request does not require a response message if the server is sending +back stream data. A non-unary stream may return a single response message but no +other stream data may follow. + +#### Response Flags + +No response flags are defined at this time, flags should be empty. + +### Data + +The data message is used to send data on an already initialized stream. Either +client or server may send data. A data message is not allowed on a unary stream. +A data message should not be sent after indicating `remote closed` to the peer. +The last data message on a stream must set the `remote closed` flag. + +The `no data` flag is used to indicate that the data message does not include +any data. This is normally used with the `remote closed` flag to indicate the +stream is now closed without transmitting any data. Since ttrpc normally +transmits a single object per message, a zero length data message may be +interpreted as an empty object. For example, transmitting the number zero as a +protobuf message ends up with a data length of zero, but the message is still +considered data and should be processed. + +#### Data Flags + +| Flag | Name | Description | +|------|-----------------|-----------------------------------| +| 0x01 | `remote closed` | No more data expected from remote | +| 0x04 | `no data` | This message does not have data | + +## Streaming + +All ttrpc requests use streams to transfer data. Unary streams will only have +two messages sent per stream, a request from a client and a response from the +server. Non-unary streams, however, may send any numbers of messages from the +client and the server. This makes stream management more complicated than unary +streams since both client and server need to track additional state. To keep +this management as simple as possible, ttrpc minimizes the number of states and +uses two flags instead of control frames. Each stream has two states while a +stream is still alive: `local closed` and `remote closed`. Each peer considers +local and remote from their own perspective and sets flags from the other peer's +perspective. For example, if a client sends a data frame with the +`remote closed` flag, that is indicating that the client is now `local closed` +and the server will be `remote closed`. A unary operation does not need to send +these flags since each received message always indicates `remote closed`. Once a +peer is both `local closed` and `remote closed`, the stream is considered +finished and may be cleaned up. + +Due to the asymmetric nature of the current protocol, a client should +always be in the `local closed` state before `remote closed` and a server should +always be in the `remote closed` state before `local closed`. This happens +because the client is always initiating requests and a client always expects a +final response back from a server to indicate the initiated request has been +fulfilled. This may mean server sends a final empty response to finish a stream +even after it has already completed sending data before the client. + +### Unary State Diagram + + +--------+ +--------+ + | Client | | Server | + +---+----+ +----+---+ + | +---------+ | + local >---------------+ Request +--------------------> remote + closed | +---------+ | closed + | | + | +----------+ | + finished <--------------+ Response +--------------------< finished + | +----------+ | + | | + +### Non-Unary State Diagrams + +RC: `remote closed` flag +RO: `remote open` flag + + +--------+ +--------+ + | Client | | Server | + +---+----+ +----+---+ + | +--------------+ | + >-------------+ Request [RO] +-----------------> + | +--------------+ | + | | + | +------+ | + >-----------------+ Data +---------------------> + | +------+ | + | | + | +-----------+ | + local >---------------+ Data [RC] +------------------> remote + closed | +-----------+ | closed + | | + | +----------+ | + finished <--------------+ Response +--------------------< finished + | +----------+ | + | | + + +--------+ +--------+ + | Client | | Server | + +---+----+ +----+---+ + | +--------------+ | + local >-------------+ Request [RC] +-----------------> remote + closed | +--------------+ | closed + | | + | +------+ | + <-----------------+ Data +---------------------< + | +------+ | + | | + | +-----------+ | + finished <---------------+ Data [RC] +------------------< finished + | +-----------+ | + | | + + +--------+ +--------+ + | Client | | Server | + +---+----+ +----+---+ + | +--------------+ | + >-------------+ Request [RO] +-----------------> + | +--------------+ | + | | + | +------+ | + >-----------------+ Data +---------------------> + | +------+ | + | | + | +------+ | + <-----------------+ Data +---------------------< + | +------+ | + | | + | +------+ | + >-----------------+ Data +---------------------> + | +------+ | + | | + | +-----------+ | + local >---------------+ Data [RC] +------------------> remote + closed | +-----------+ | closed + | | + | +------+ | + <-----------------+ Data +---------------------< + | +------+ | + | | + | +-----------+ | + finished <---------------+ Data [RC] +------------------< finished + | +-----------+ | + | | + +## RPC + +While this protocol is defined primarily to support Remote Procedure Calls, the +protocol does not define the request and response types beyond the messages +defined in the protocol. The implementation provides a default protobuf +definition of request and response which may be used for cross language rpc. +All implementations should at least define a request type which support +routing by procedure name and a response type which supports call status. + +## Version History + +| Version | Features | +|---------|---------------------| +| 1.0 | Unary requests only | +| 1.2 | Streaming support | diff --git a/vendor/github.com/containerd/ttrpc/Protobuild.toml b/vendor/github.com/containerd/ttrpc/Protobuild.toml new file mode 100644 index 000000000..0f6ccbd1e --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/Protobuild.toml @@ -0,0 +1,28 @@ +version = "2" +generators = ["go"] + +# Control protoc include paths. Below are usually some good defaults, but feel +# free to try it without them if it works for your project. +[includes] + # Include paths that will be added before all others. Typically, you want to + # treat the root of the project as an include, but this may not be necessary. + before = ["."] + + # Paths that will be added untouched to the end of the includes. We use + # `/usr/local/include` to pickup the common install location of protobuf. + # This is the default. + after = ["/usr/local/include"] + +# This section maps protobuf imports to Go packages. These will become +# `-M` directives in the call to the go protobuf generator. +[packages] + "google/protobuf/any.proto" = "github.com/gogo/protobuf/types" + "proto/status.proto" = "google.golang.org/genproto/googleapis/rpc/status" + +[[overrides]] +# enable ttrpc and disable fieldpath and grpc for the shim +prefixes = ["github.com/containerd/ttrpc/integration/streaming"] +generators = ["go", "go-ttrpc"] + +[overrides.parameters.go-ttrpc] +prefix = "TTRPC" diff --git a/vendor/github.com/containerd/ttrpc/README.md b/vendor/github.com/containerd/ttrpc/README.md new file mode 100644 index 000000000..ce95f63be --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/README.md @@ -0,0 +1,59 @@ +# ttrpc + +[![Build Status](https://github.com/containerd/ttrpc/actions/workflows/ci.yml/badge.svg)](https://github.com/containerd/ttrpc/actions/workflows/ci.yml) + +GRPC for low-memory environments. + +The existing grpc-go project requires a lot of memory overhead for importing +packages and at runtime. While this is great for many services with low density +requirements, this can be a problem when running a large number of services on +a single machine or on a machine with a small amount of memory. + +Using the same GRPC definitions, this project reduces the binary size and +protocol overhead required. We do this by eliding the `net/http`, `net/http2` +and `grpc` package used by grpc replacing it with a lightweight framing +protocol. The result are smaller binaries that use less resident memory with +the same ease of use as GRPC. + +Please note that while this project supports generating either end of the +protocol, the generated service definitions will be incompatible with regular +GRPC services, as they do not speak the same protocol. + +# Protocol + +See the [protocol specification](./PROTOCOL.md). + +# Usage + +Create a gogo vanity binary (see +[`cmd/protoc-gen-gogottrpc/main.go`](cmd/protoc-gen-gogottrpc/main.go) for an +example with the ttrpc plugin enabled. + +It's recommended to use [`protobuild`](https://github.com/containerd/protobuild) +to build the protobufs for this project, but this will work with protoc +directly, if required. + +# Differences from GRPC + +- The protocol stack has been replaced with a lighter protocol that doesn't + require http, http2 and tls. +- The client and server interface are identical whereas in GRPC there is a + client and server interface that are different. +- The Go stdlib context package is used instead. + +# Status + +TODO: + +- [ ] Add testing under concurrent load to ensure +- [ ] Verify connection error handling + +# Project details + +ttrpc is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd sub-project, you will find the: + * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), + * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), + * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) + +information in our [`containerd/project`](https://github.com/containerd/project) repository. diff --git a/vendor/github.com/containerd/ttrpc/channel.go b/vendor/github.com/containerd/ttrpc/channel.go new file mode 100644 index 000000000..872261e6d --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/channel.go @@ -0,0 +1,182 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "net" + "sync" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + messageHeaderLength = 10 + messageLengthMax = 4 << 20 +) + +type messageType uint8 + +const ( + messageTypeRequest messageType = 0x1 + messageTypeResponse messageType = 0x2 + messageTypeData messageType = 0x3 +) + +func (mt messageType) String() string { + switch mt { + case messageTypeRequest: + return "request" + case messageTypeResponse: + return "response" + case messageTypeData: + return "data" + default: + return "unknown" + } +} + +const ( + flagRemoteClosed uint8 = 0x1 + flagRemoteOpen uint8 = 0x2 + flagNoData uint8 = 0x4 +) + +// messageHeader represents the fixed-length message header of 10 bytes sent +// with every request. +type messageHeader struct { + Length uint32 // length excluding this header. b[:4] + StreamID uint32 // identifies which request stream message is a part of. b[4:8] + Type messageType // message type b[8] + Flags uint8 // type specific flags b[9] +} + +func readMessageHeader(p []byte, r io.Reader) (messageHeader, error) { + _, err := io.ReadFull(r, p[:messageHeaderLength]) + if err != nil { + return messageHeader{}, err + } + + return messageHeader{ + Length: binary.BigEndian.Uint32(p[:4]), + StreamID: binary.BigEndian.Uint32(p[4:8]), + Type: messageType(p[8]), + Flags: p[9], + }, nil +} + +func writeMessageHeader(w io.Writer, p []byte, mh messageHeader) error { + binary.BigEndian.PutUint32(p[:4], mh.Length) + binary.BigEndian.PutUint32(p[4:8], mh.StreamID) + p[8] = byte(mh.Type) + p[9] = mh.Flags + + _, err := w.Write(p[:]) + return err +} + +var buffers sync.Pool + +type channel struct { + conn net.Conn + bw *bufio.Writer + br *bufio.Reader + hrbuf [messageHeaderLength]byte // avoid alloc when reading header + hwbuf [messageHeaderLength]byte +} + +func newChannel(conn net.Conn) *channel { + return &channel{ + conn: conn, + bw: bufio.NewWriter(conn), + br: bufio.NewReader(conn), + } +} + +// recv a message from the channel. The returned buffer contains the message. +// +// If a valid grpc status is returned, the message header +// returned will be valid and caller should send that along to +// the correct consumer. The bytes on the underlying channel +// will be discarded. +func (ch *channel) recv() (messageHeader, []byte, error) { + mh, err := readMessageHeader(ch.hrbuf[:], ch.br) + if err != nil { + return messageHeader{}, nil, err + } + + if mh.Length > uint32(messageLengthMax) { + if _, err := ch.br.Discard(int(mh.Length)); err != nil { + return mh, nil, fmt.Errorf("failed to discard after receiving oversized message: %w", err) + } + + return mh, nil, status.Errorf(codes.ResourceExhausted, "message length %v exceed maximum message size of %v", mh.Length, messageLengthMax) + } + + var p []byte + if mh.Length > 0 { + p = ch.getmbuf(int(mh.Length)) + if _, err := io.ReadFull(ch.br, p); err != nil { + return messageHeader{}, nil, fmt.Errorf("failed reading message: %w", err) + } + } + + return mh, p, nil +} + +func (ch *channel) send(streamID uint32, t messageType, flags uint8, p []byte) error { + if len(p) > messageLengthMax { + return OversizedMessageError(len(p)) + } + + if err := writeMessageHeader(ch.bw, ch.hwbuf[:], messageHeader{Length: uint32(len(p)), StreamID: streamID, Type: t, Flags: flags}); err != nil { + return err + } + + if len(p) > 0 { + _, err := ch.bw.Write(p) + if err != nil { + return err + } + } + + return ch.bw.Flush() +} + +func (ch *channel) getmbuf(size int) []byte { + // we can't use the standard New method on pool because we want to allocate + // based on size. + b, ok := buffers.Get().(*[]byte) + if !ok || cap(*b) < size { + // TODO(stevvooe): It may be better to allocate these in fixed length + // buckets to reduce fragmentation but its not clear that would help + // with performance. An ilogb approach or similar would work well. + bb := make([]byte, size) + b = &bb + } else { + *b = (*b)[:size] + } + return *b +} + +func (ch *channel) putmbuf(p []byte) { + buffers.Put(&p) +} diff --git a/vendor/github.com/containerd/ttrpc/client.go b/vendor/github.com/containerd/ttrpc/client.go new file mode 100644 index 000000000..be20ed489 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/client.go @@ -0,0 +1,571 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "strings" + "sync" + "syscall" + "time" + + "github.com/containerd/log" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Client for a ttrpc server +type Client struct { + codec codec + conn net.Conn + channel *channel + + streamLock sync.RWMutex + streams map[streamID]*stream + nextStreamID streamID + sendLock sync.Mutex + + ctx context.Context + closed func() + + closeOnce sync.Once + userCloseFunc func() + userCloseWaitCh chan struct{} + + interceptor UnaryClientInterceptor +} + +// ClientOpts configures a client +type ClientOpts func(c *Client) + +// WithOnClose sets the close func whenever the client's Close() method is called +func WithOnClose(onClose func()) ClientOpts { + return func(c *Client) { + c.userCloseFunc = onClose + } +} + +// WithUnaryClientInterceptor sets the provided client interceptor +func WithUnaryClientInterceptor(i UnaryClientInterceptor) ClientOpts { + return func(c *Client) { + c.interceptor = i + } +} + +// WithChainUnaryClientInterceptor sets the provided chain of client interceptors +func WithChainUnaryClientInterceptor(interceptors ...UnaryClientInterceptor) ClientOpts { + return func(c *Client) { + if len(interceptors) == 0 { + return + } + if c.interceptor != nil { + interceptors = append([]UnaryClientInterceptor{c.interceptor}, interceptors...) + } + c.interceptor = func( + ctx context.Context, + req *Request, + reply *Response, + info *UnaryClientInfo, + final Invoker, + ) error { + return interceptors[0](ctx, req, reply, info, + chainUnaryInterceptors(interceptors[1:], final, info)) + } + } +} + +func chainUnaryInterceptors(interceptors []UnaryClientInterceptor, final Invoker, info *UnaryClientInfo) Invoker { + if len(interceptors) == 0 { + return final + } + return func( + ctx context.Context, + req *Request, + reply *Response, + ) error { + return interceptors[0](ctx, req, reply, info, + chainUnaryInterceptors(interceptors[1:], final, info)) + } +} + +// NewClient creates a new ttrpc client using the given connection +func NewClient(conn net.Conn, opts ...ClientOpts) *Client { + ctx, cancel := context.WithCancel(context.Background()) + channel := newChannel(conn) + c := &Client{ + codec: codec{}, + conn: conn, + channel: channel, + streams: make(map[streamID]*stream), + nextStreamID: 1, + closed: cancel, + ctx: ctx, + userCloseFunc: func() {}, + userCloseWaitCh: make(chan struct{}), + } + + for _, o := range opts { + o(c) + } + + if c.interceptor == nil { + c.interceptor = defaultClientInterceptor + } + + go c.run() + return c +} + +func (c *Client) send(sid uint32, mt messageType, flags uint8, b []byte) error { + c.sendLock.Lock() + defer c.sendLock.Unlock() + return c.channel.send(sid, mt, flags, b) +} + +// Call makes a unary request and returns with response +func (c *Client) Call(ctx context.Context, service, method string, req, resp interface{}) error { + payload, err := c.codec.Marshal(req) + if err != nil { + return err + } + + var ( + creq = &Request{ + Service: service, + Method: method, + Payload: payload, + // TODO: metadata from context + } + + cresp = &Response{} + ) + + if metadata, ok := GetMetadata(ctx); ok { + metadata.setRequest(creq) + } + + if dl, ok := ctx.Deadline(); ok { + creq.TimeoutNano = time.Until(dl).Nanoseconds() + } + + info := &UnaryClientInfo{ + FullMethod: fullPath(service, method), + } + if err := c.interceptor(ctx, creq, cresp, info, c.dispatch); err != nil { + return err + } + + if err := c.codec.Unmarshal(cresp.Payload, resp); err != nil { + return err + } + + if cresp.Status != nil && cresp.Status.Code != int32(codes.OK) { + return status.ErrorProto(cresp.Status) + } + return nil +} + +// StreamDesc describes the stream properties, whether the stream has +// a streaming client, a streaming server, or both +type StreamDesc struct { + StreamingClient bool + StreamingServer bool +} + +// ClientStream is used to send or recv messages on the underlying stream +type ClientStream interface { + CloseSend() error + SendMsg(m interface{}) error + RecvMsg(m interface{}) error +} + +type clientStream struct { + ctx context.Context + s *stream + c *Client + desc *StreamDesc + localClosed bool + remoteClosed bool +} + +func (cs *clientStream) CloseSend() error { + if !cs.desc.StreamingClient { + return fmt.Errorf("%w: cannot close non-streaming client", ErrProtocol) + } + if cs.localClosed { + return ErrStreamClosed + } + err := cs.s.send(messageTypeData, flagRemoteClosed|flagNoData, nil) + if err != nil { + return filterCloseErr(err) + } + cs.localClosed = true + return nil +} + +func (cs *clientStream) SendMsg(m interface{}) error { + if !cs.desc.StreamingClient { + return fmt.Errorf("%w: cannot send data from non-streaming client", ErrProtocol) + } + if cs.localClosed { + return ErrStreamClosed + } + + var ( + payload []byte + err error + ) + if m != nil { + payload, err = cs.c.codec.Marshal(m) + if err != nil { + return err + } + } + + err = cs.s.send(messageTypeData, 0, payload) + if err != nil { + return filterCloseErr(err) + } + + return nil +} + +func (cs *clientStream) RecvMsg(m interface{}) error { + if cs.remoteClosed { + return io.EOF + } + + var msg *streamMessage + select { + case <-cs.ctx.Done(): + return cs.ctx.Err() + case <-cs.s.recvClose: + // If recv has a pending message, process that first + select { + case msg = <-cs.s.recv: + default: + return cs.s.recvErr + } + case msg = <-cs.s.recv: + } + + switch msg.header.Type { + case messageTypeResponse: + resp := &Response{} + err := proto.Unmarshal(msg.payload[:msg.header.Length], resp) + // return the payload buffer for reuse + cs.c.channel.putmbuf(msg.payload) + if err != nil { + return err + } + + if err := cs.c.codec.Unmarshal(resp.Payload, m); err != nil { + return err + } + + if resp.Status != nil && resp.Status.Code != int32(codes.OK) { + return status.ErrorProto(resp.Status) + } + + cs.c.deleteStream(cs.s) + cs.remoteClosed = true + + return nil + case messageTypeData: + if !cs.desc.StreamingServer { + cs.c.deleteStream(cs.s) + cs.remoteClosed = true + return fmt.Errorf("received data from non-streaming server: %w", ErrProtocol) + } + if msg.header.Flags&flagRemoteClosed == flagRemoteClosed { + cs.c.deleteStream(cs.s) + cs.remoteClosed = true + + if msg.header.Flags&flagNoData == flagNoData { + return io.EOF + } + } + + err := cs.c.codec.Unmarshal(msg.payload[:msg.header.Length], m) + cs.c.channel.putmbuf(msg.payload) + if err != nil { + return err + } + return nil + default: + return fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) + } +} + +// Close closes the ttrpc connection and underlying connection +func (c *Client) Close() error { + c.closeOnce.Do(func() { + c.closed() + + c.conn.Close() + }) + return nil +} + +// UserOnCloseWait is used to block until the user's on-close callback +// finishes. +func (c *Client) UserOnCloseWait(ctx context.Context) error { + select { + case <-c.userCloseWaitCh: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (c *Client) run() { + err := c.receiveLoop() + c.Close() + c.cleanupStreams(err) + + c.userCloseFunc() + close(c.userCloseWaitCh) +} + +func (c *Client) receiveLoop() error { + for { + select { + case <-c.ctx.Done(): + return ErrClosed + default: + var ( + msg = &streamMessage{} + err error + ) + + msg.header, msg.payload, err = c.channel.recv() + if err != nil { + _, ok := status.FromError(err) + if !ok { + // treat all errors that are not an rpc status as terminal. + // all others poison the connection. + return filterCloseErr(err) + } + } + sid := streamID(msg.header.StreamID) + s := c.getStream(sid) + if s == nil { + log.G(c.ctx).WithField("stream", sid).Error("ttrpc: received message on inactive stream") + continue + } + + if err != nil { + s.closeWithError(err) + } else { + if err := s.receive(c.ctx, msg); err != nil { + log.G(c.ctx).WithFields(log.Fields{"error": err, "stream": sid}).Error("ttrpc: failed to handle message") + } + } + } + } +} + +// createStream creates a new stream and registers it with the client +// Introduce stream types for multiple or single response +func (c *Client) createStream(flags uint8, b []byte) (*stream, error) { + // sendLock must be held across both allocation of the stream ID and sending it across the wire. + // This ensures that new stream IDs sent on the wire are always increasing, which is a + // requirement of the TTRPC protocol. + // This use of sendLock could be split into another mutex that covers stream creation + first send, + // and just use sendLock to guard writing to the wire, but for now it seems simpler to have fewer mutexes. + c.sendLock.Lock() + defer c.sendLock.Unlock() + + // Check if closed since lock acquired to prevent adding + // anything after cleanup completes + select { + case <-c.ctx.Done(): + return nil, ErrClosed + default: + } + + var s *stream + if err := func() error { + // In the future this could be replaced with a sync.Map instead of streamLock+map. + c.streamLock.Lock() + defer c.streamLock.Unlock() + + // Check if closed since lock acquired to prevent adding + // anything after cleanup completes + select { + case <-c.ctx.Done(): + return ErrClosed + default: + } + + s = newStream(c.nextStreamID, c) + c.streams[s.id] = s + c.nextStreamID = c.nextStreamID + 2 + + return nil + }(); err != nil { + return nil, err + } + + if err := c.channel.send(uint32(s.id), messageTypeRequest, flags, b); err != nil { + return s, filterCloseErr(err) + } + + return s, nil +} + +func (c *Client) deleteStream(s *stream) { + c.streamLock.Lock() + delete(c.streams, s.id) + c.streamLock.Unlock() + s.closeWithError(nil) +} + +func (c *Client) getStream(sid streamID) *stream { + c.streamLock.RLock() + s := c.streams[sid] + c.streamLock.RUnlock() + return s +} + +func (c *Client) cleanupStreams(err error) { + c.streamLock.Lock() + defer c.streamLock.Unlock() + + for sid, s := range c.streams { + s.closeWithError(err) + delete(c.streams, sid) + } +} + +// filterCloseErr rewrites EOF and EPIPE errors to ErrClosed. Use when +// returning from call or handling errors from main read loop. +// +// This purposely ignores errors with a wrapped cause. +func filterCloseErr(err error) error { + switch { + case err == nil: + return nil + case err == io.EOF: + return ErrClosed + case errors.Is(err, io.ErrClosedPipe): + return ErrClosed + case errors.Is(err, io.EOF): + return ErrClosed + case strings.Contains(err.Error(), "use of closed network connection"): + return ErrClosed + default: + // if we have an epipe on a write or econnreset on a read , we cast to errclosed + var oerr *net.OpError + if errors.As(err, &oerr) { + if (oerr.Op == "write" && errors.Is(err, syscall.EPIPE)) || + (oerr.Op == "read" && errors.Is(err, syscall.ECONNRESET)) { + return ErrClosed + } + } + } + + return err +} + +// NewStream creates a new stream with the given stream descriptor to the +// specified service and method. If not a streaming client, the request object +// may be provided. +func (c *Client) NewStream(ctx context.Context, desc *StreamDesc, service, method string, req interface{}) (ClientStream, error) { + var payload []byte + if req != nil { + var err error + payload, err = c.codec.Marshal(req) + if err != nil { + return nil, err + } + } + + request := &Request{ + Service: service, + Method: method, + Payload: payload, + // TODO: metadata from context + } + p, err := c.codec.Marshal(request) + if err != nil { + return nil, err + } + + var flags uint8 + if desc.StreamingClient { + flags = flagRemoteOpen + } else { + flags = flagRemoteClosed + } + s, err := c.createStream(flags, p) + if err != nil { + return nil, err + } + + return &clientStream{ + ctx: ctx, + s: s, + c: c, + desc: desc, + }, nil +} + +func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error { + p, err := c.codec.Marshal(req) + if err != nil { + return err + } + + s, err := c.createStream(0, p) + if err != nil { + return err + } + defer c.deleteStream(s) + + var msg *streamMessage + select { + case <-ctx.Done(): + return ctx.Err() + case <-c.ctx.Done(): + return ErrClosed + case <-s.recvClose: + // If recv has a pending message, process that first + select { + case msg = <-s.recv: + default: + return s.recvErr + } + case msg = <-s.recv: + } + + if msg.header.Type == messageTypeResponse { + err = proto.Unmarshal(msg.payload[:msg.header.Length], resp) + } else { + err = fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) + } + + // return the payload buffer for reuse + c.channel.putmbuf(msg.payload) + + return err +} diff --git a/vendor/github.com/containerd/ttrpc/codec.go b/vendor/github.com/containerd/ttrpc/codec.go new file mode 100644 index 000000000..3e82722a4 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/codec.go @@ -0,0 +1,43 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "fmt" + + "google.golang.org/protobuf/proto" +) + +type codec struct{} + +func (c codec) Marshal(msg interface{}) ([]byte, error) { + switch v := msg.(type) { + case proto.Message: + return proto.Marshal(v) + default: + return nil, fmt.Errorf("ttrpc: cannot marshal unknown type: %T", msg) + } +} + +func (c codec) Unmarshal(p []byte, msg interface{}) error { + switch v := msg.(type) { + case proto.Message: + return proto.Unmarshal(p, v) + default: + return fmt.Errorf("ttrpc: cannot unmarshal into unknown type: %T", msg) + } +} diff --git a/vendor/github.com/containerd/ttrpc/config.go b/vendor/github.com/containerd/ttrpc/config.go new file mode 100644 index 000000000..f401f67be --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/config.go @@ -0,0 +1,86 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "errors" +) + +type serverConfig struct { + handshaker Handshaker + interceptor UnaryServerInterceptor +} + +// ServerOpt for configuring a ttrpc server +type ServerOpt func(*serverConfig) error + +// WithServerHandshaker can be passed to NewServer to ensure that the +// handshaker is called before every connection attempt. +// +// Only one handshaker is allowed per server. +func WithServerHandshaker(handshaker Handshaker) ServerOpt { + return func(c *serverConfig) error { + if c.handshaker != nil { + return errors.New("only one handshaker allowed per server") + } + c.handshaker = handshaker + return nil + } +} + +// WithUnaryServerInterceptor sets the provided interceptor on the server +func WithUnaryServerInterceptor(i UnaryServerInterceptor) ServerOpt { + return func(c *serverConfig) error { + if c.interceptor != nil { + return errors.New("only one unchained interceptor allowed per server") + } + c.interceptor = i + return nil + } +} + +// WithChainUnaryServerInterceptor sets the provided chain of server interceptors +func WithChainUnaryServerInterceptor(interceptors ...UnaryServerInterceptor) ServerOpt { + return func(c *serverConfig) error { + if len(interceptors) == 0 { + return nil + } + if c.interceptor != nil { + interceptors = append([]UnaryServerInterceptor{c.interceptor}, interceptors...) + } + c.interceptor = func( + ctx context.Context, + unmarshal Unmarshaler, + info *UnaryServerInfo, + method Method) (interface{}, error) { + return interceptors[0](ctx, unmarshal, info, + chainUnaryServerInterceptors(info, method, interceptors[1:])) + } + return nil + } +} + +func chainUnaryServerInterceptors(info *UnaryServerInfo, method Method, interceptors []UnaryServerInterceptor) Method { + if len(interceptors) == 0 { + return method + } + return func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { + return interceptors[0](ctx, unmarshal, info, + chainUnaryServerInterceptors(info, method, interceptors[1:])) + } +} diff --git a/vendor/github.com/containerd/ttrpc/doc.go b/vendor/github.com/containerd/ttrpc/doc.go new file mode 100644 index 000000000..d80cd424c --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/doc.go @@ -0,0 +1,23 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* +package ttrpc defines and implements a low level simple transfer protocol +optimized for low latency and reliable connections between processes on the same +host. The protocol uses simple framing for sending requests, responses, and data +using multiple streams. +*/ +package ttrpc diff --git a/vendor/github.com/containerd/ttrpc/errors.go b/vendor/github.com/containerd/ttrpc/errors.go new file mode 100644 index 000000000..632dbe8bd --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/errors.go @@ -0,0 +1,80 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "errors" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + // ErrProtocol is a general error in the handling the protocol. + ErrProtocol = errors.New("protocol error") + + // ErrClosed is returned by client methods when the underlying connection is + // closed. + ErrClosed = errors.New("ttrpc: closed") + + // ErrServerClosed is returned when the Server has closed its connection. + ErrServerClosed = errors.New("ttrpc: server closed") + + // ErrStreamClosed is when the streaming connection is closed. + ErrStreamClosed = errors.New("ttrpc: stream closed") +) + +// OversizedMessageErr is used to indicate refusal to send an oversized message. +// It wraps a ResourceExhausted grpc Status together with the offending message +// length. +type OversizedMessageErr struct { + messageLength int + err error +} + +// OversizedMessageError returns an OversizedMessageErr error for the given message +// length if it exceeds the allowed maximum. Otherwise a nil error is returned. +func OversizedMessageError(messageLength int) error { + if messageLength <= messageLengthMax { + return nil + } + + return &OversizedMessageErr{ + messageLength: messageLength, + err: status.Errorf(codes.ResourceExhausted, "message length %v exceed maximum message size of %v", messageLength, messageLengthMax), + } +} + +// Error returns the error message for the corresponding grpc Status for the error. +func (e *OversizedMessageErr) Error() string { + return e.err.Error() +} + +// Unwrap returns the corresponding error with our grpc status code. +func (e *OversizedMessageErr) Unwrap() error { + return e.err +} + +// RejectedLength retrieves the rejected message length which triggered the error. +func (e *OversizedMessageErr) RejectedLength() int { + return e.messageLength +} + +// MaximumLength retrieves the maximum allowed message length that triggered the error. +func (*OversizedMessageErr) MaximumLength() int { + return messageLengthMax +} diff --git a/vendor/github.com/containerd/ttrpc/handshake.go b/vendor/github.com/containerd/ttrpc/handshake.go new file mode 100644 index 000000000..3c6b610d3 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/handshake.go @@ -0,0 +1,50 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "net" +) + +// Handshaker defines the interface for connection handshakes performed on the +// server or client when first connecting. +type Handshaker interface { + // Handshake should confirm or decorate a connection that may be incoming + // to a server or outgoing from a client. + // + // If this returns without an error, the caller should use the connection + // in place of the original connection. + // + // The second return value can contain credential specific data, such as + // unix socket credentials or TLS information. + // + // While we currently only have implementations on the server-side, this + // interface should be sufficient to implement similar handshakes on the + // client-side. + Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) +} + +type handshakerFunc func(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) + +func (fn handshakerFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) { + return fn(ctx, conn) +} + +func noopHandshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) { + return conn, nil, nil +} diff --git a/vendor/github.com/containerd/ttrpc/interceptor.go b/vendor/github.com/containerd/ttrpc/interceptor.go new file mode 100644 index 000000000..7ff5e9d33 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/interceptor.go @@ -0,0 +1,65 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import "context" + +// UnaryServerInfo provides information about the server request +type UnaryServerInfo struct { + FullMethod string +} + +// UnaryClientInfo provides information about the client request +type UnaryClientInfo struct { + FullMethod string +} + +// StreamServerInfo provides information about the server request +type StreamServerInfo struct { + FullMethod string + StreamingClient bool + StreamingServer bool +} + +// Unmarshaler contains the server request data and allows it to be unmarshaled +// into a concrete type +type Unmarshaler func(interface{}) error + +// Invoker invokes the client's request and response from the ttrpc server +type Invoker func(context.Context, *Request, *Response) error + +// UnaryServerInterceptor specifies the interceptor function for server request/response +type UnaryServerInterceptor func(context.Context, Unmarshaler, *UnaryServerInfo, Method) (interface{}, error) + +// UnaryClientInterceptor specifies the interceptor function for client request/response +type UnaryClientInterceptor func(context.Context, *Request, *Response, *UnaryClientInfo, Invoker) error + +func defaultServerInterceptor(ctx context.Context, unmarshal Unmarshaler, _ *UnaryServerInfo, method Method) (interface{}, error) { + return method(ctx, unmarshal) +} + +func defaultClientInterceptor(ctx context.Context, req *Request, resp *Response, _ *UnaryClientInfo, invoker Invoker) error { + return invoker(ctx, req, resp) +} + +type StreamServerInterceptor func(context.Context, StreamServer, *StreamServerInfo, StreamHandler) (interface{}, error) + +func defaultStreamServerInterceptor(ctx context.Context, ss StreamServer, _ *StreamServerInfo, stream StreamHandler) (interface{}, error) { + return stream(ctx, ss) +} + +type StreamClientInterceptor func(context.Context) diff --git a/vendor/github.com/containerd/ttrpc/metadata.go b/vendor/github.com/containerd/ttrpc/metadata.go new file mode 100644 index 000000000..6e0042487 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/metadata.go @@ -0,0 +1,135 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "strings" +) + +// MD is the user type for ttrpc metadata +type MD map[string][]string + +// Get returns the metadata for a given key when they exist. +// If there is no metadata, a nil slice and false are returned. +func (m MD) Get(key string) ([]string, bool) { + key = strings.ToLower(key) + list, ok := m[key] + if !ok || len(list) == 0 { + return nil, false + } + + return list, true +} + +// Set sets the provided values for a given key. +// The values will overwrite any existing values. +// If no values provided, a key will be deleted. +func (m MD) Set(key string, values ...string) { + key = strings.ToLower(key) + if len(values) == 0 { + delete(m, key) + return + } + m[key] = values +} + +// Append appends additional values to the given key. +func (m MD) Append(key string, values ...string) { + key = strings.ToLower(key) + if len(values) == 0 { + return + } + current, ok := m[key] + if ok { + m.Set(key, append(current, values...)...) + } else { + m.Set(key, values...) + } +} + +// Clone returns a copy of MD or nil if it's nil. +// It's copied from golang's `http.Header.Clone` implementation: +// https://cs.opensource.google/go/go/+/refs/tags/go1.23.4:src/net/http/header.go;l=94 +func (m MD) Clone() MD { + if m == nil { + return nil + } + + // Find total number of values. + nv := 0 + for _, vv := range m { + nv += len(vv) + } + sv := make([]string, nv) // shared backing array for headers' values + m2 := make(MD, len(m)) + for k, vv := range m { + if vv == nil { + // Preserve nil values. + m2[k] = nil + continue + } + n := copy(sv, vv) + m2[k] = sv[:n:n] + sv = sv[n:] + } + return m2 +} + +func (m MD) setRequest(r *Request) { + for k, values := range m { + for _, v := range values { + r.Metadata = append(r.Metadata, &KeyValue{ + Key: k, + Value: v, + }) + } + } +} + +func (m MD) fromRequest(r *Request) { + for _, kv := range r.Metadata { + m[kv.Key] = append(m[kv.Key], kv.Value) + } +} + +type metadataKey struct{} + +// GetMetadata retrieves metadata from context.Context (previously attached with WithMetadata) +func GetMetadata(ctx context.Context) (MD, bool) { + metadata, ok := ctx.Value(metadataKey{}).(MD) + return metadata, ok +} + +// GetMetadataValue gets a specific metadata value by name from context.Context +func GetMetadataValue(ctx context.Context, name string) (string, bool) { + metadata, ok := GetMetadata(ctx) + if !ok { + return "", false + } + + if list, ok := metadata.Get(name); ok { + return list[0], true + } + + return "", false +} + +// WithMetadata attaches metadata map to a context.Context +func WithMetadata(ctx context.Context, md MD) context.Context { + return context.WithValue(ctx, metadataKey{}, md) +} diff --git a/vendor/github.com/containerd/ttrpc/request.pb.go b/vendor/github.com/containerd/ttrpc/request.pb.go new file mode 100644 index 000000000..3921ae5a3 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/request.pb.go @@ -0,0 +1,396 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.20.1 +// source: github.com/containerd/ttrpc/request.proto + +package ttrpc + +import ( + status "google.golang.org/genproto/googleapis/rpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Request struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` + Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` + TimeoutNano int64 `protobuf:"varint,4,opt,name=timeout_nano,json=timeoutNano,proto3" json:"timeout_nano,omitempty"` + Metadata []*KeyValue `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty"` +} + +func (x *Request) Reset() { + *x = Request{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Request) ProtoMessage() {} + +func (x *Request) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Request.ProtoReflect.Descriptor instead. +func (*Request) Descriptor() ([]byte, []int) { + return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{0} +} + +func (x *Request) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *Request) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *Request) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *Request) GetTimeoutNano() int64 { + if x != nil { + return x.TimeoutNano + } + return 0 +} + +func (x *Request) GetMetadata() []*KeyValue { + if x != nil { + return x.Metadata + } + return nil +} + +type Response struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status *status.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (x *Response) Reset() { + *x = Response{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response) ProtoMessage() {} + +func (x *Response) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Response.ProtoReflect.Descriptor instead. +func (*Response) Descriptor() ([]byte, []int) { + return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{1} +} + +func (x *Response) GetStatus() *status.Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *Response) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +type StringList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + List []string `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *StringList) Reset() { + *x = StringList{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StringList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringList) ProtoMessage() {} + +func (x *StringList) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringList.ProtoReflect.Descriptor instead. +func (*StringList) Descriptor() ([]byte, []int) { + return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{2} +} + +func (x *StringList) GetList() []string { + if x != nil { + return x.List + } + return nil +} + +type KeyValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *KeyValue) Reset() { + *x = KeyValue{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValue) ProtoMessage() {} + +func (x *KeyValue) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead. +func (*KeyValue) Descriptor() ([]byte, []int) { + return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{3} +} + +func (x *KeyValue) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *KeyValue) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +var File_github_com_containerd_ttrpc_request_proto protoreflect.FileDescriptor + +var file_github_com_containerd_ttrpc_request_proto_rawDesc = []byte{ + 0x0a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x74, 0x72, + 0x70, 0x63, 0x1a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa5, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x21, + 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4e, 0x61, 0x6e, + 0x6f, 0x12, 0x2b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x45, + 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, + 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, + 0x6c, 0x75, 0x65, 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, 0x42, 0x1d, 0x5a, 0x1b, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_github_com_containerd_ttrpc_request_proto_rawDescOnce sync.Once + file_github_com_containerd_ttrpc_request_proto_rawDescData = file_github_com_containerd_ttrpc_request_proto_rawDesc +) + +func file_github_com_containerd_ttrpc_request_proto_rawDescGZIP() []byte { + file_github_com_containerd_ttrpc_request_proto_rawDescOnce.Do(func() { + file_github_com_containerd_ttrpc_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_request_proto_rawDescData) + }) + return file_github_com_containerd_ttrpc_request_proto_rawDescData +} + +var file_github_com_containerd_ttrpc_request_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_github_com_containerd_ttrpc_request_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: ttrpc.Request + (*Response)(nil), // 1: ttrpc.Response + (*StringList)(nil), // 2: ttrpc.StringList + (*KeyValue)(nil), // 3: ttrpc.KeyValue + (*status.Status)(nil), // 4: Status +} +var file_github_com_containerd_ttrpc_request_proto_depIdxs = []int32{ + 3, // 0: ttrpc.Request.metadata:type_name -> ttrpc.KeyValue + 4, // 1: ttrpc.Response.status:type_name -> Status + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_github_com_containerd_ttrpc_request_proto_init() } +func file_github_com_containerd_ttrpc_request_proto_init() { + if File_github_com_containerd_ttrpc_request_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_github_com_containerd_ttrpc_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_github_com_containerd_ttrpc_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Response); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_github_com_containerd_ttrpc_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StringList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_github_com_containerd_ttrpc_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_github_com_containerd_ttrpc_request_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_github_com_containerd_ttrpc_request_proto_goTypes, + DependencyIndexes: file_github_com_containerd_ttrpc_request_proto_depIdxs, + MessageInfos: file_github_com_containerd_ttrpc_request_proto_msgTypes, + }.Build() + File_github_com_containerd_ttrpc_request_proto = out.File + file_github_com_containerd_ttrpc_request_proto_rawDesc = nil + file_github_com_containerd_ttrpc_request_proto_goTypes = nil + file_github_com_containerd_ttrpc_request_proto_depIdxs = nil +} diff --git a/vendor/github.com/containerd/ttrpc/request.proto b/vendor/github.com/containerd/ttrpc/request.proto new file mode 100644 index 000000000..37da334fc --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/request.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package ttrpc; + +import "proto/status.proto"; + +option go_package = "github.com/containerd/ttrpc"; + +message Request { + string service = 1; + string method = 2; + bytes payload = 3; + int64 timeout_nano = 4; + repeated KeyValue metadata = 5; +} + +message Response { + Status status = 1; + bytes payload = 2; +} + +message StringList { + repeated string list = 1; +} + +message KeyValue { + string key = 1; + string value = 2; +} diff --git a/vendor/github.com/containerd/ttrpc/server.go b/vendor/github.com/containerd/ttrpc/server.go new file mode 100644 index 000000000..9606a975d --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/server.go @@ -0,0 +1,587 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "errors" + "io" + "math/rand" + "net" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/containerd/log" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type Server struct { + config *serverConfig + services *serviceSet + codec codec + + mu sync.Mutex + listeners map[net.Listener]struct{} + connections map[*serverConn]struct{} // all connections to current state + done chan struct{} // marks point at which we stop serving requests +} + +func NewServer(opts ...ServerOpt) (*Server, error) { + config := &serverConfig{} + for _, opt := range opts { + if err := opt(config); err != nil { + return nil, err + } + } + if config.interceptor == nil { + config.interceptor = defaultServerInterceptor + } + + return &Server{ + config: config, + services: newServiceSet(config.interceptor), + done: make(chan struct{}), + listeners: make(map[net.Listener]struct{}), + connections: make(map[*serverConn]struct{}), + }, nil +} + +// Register registers a map of methods to method handlers +// TODO: Remove in 2.0, does not support streams +func (s *Server) Register(name string, methods map[string]Method) { + s.services.register(name, &ServiceDesc{Methods: methods}) +} + +func (s *Server) RegisterService(name string, desc *ServiceDesc) { + s.services.register(name, desc) +} + +func (s *Server) Serve(ctx context.Context, l net.Listener) error { + s.mu.Lock() + s.addListenerLocked(l) + defer s.closeListener(l) + + select { + case <-s.done: + s.mu.Unlock() + return ErrServerClosed + default: + } + s.mu.Unlock() + + var ( + backoff time.Duration + handshaker = s.config.handshaker + ) + + if handshaker == nil { + handshaker = handshakerFunc(noopHandshake) + } + + for { + conn, err := l.Accept() + if err != nil { + select { + case <-s.done: + return ErrServerClosed + default: + } + + if terr, ok := err.(interface { + Temporary() bool + }); ok && terr.Temporary() { + if backoff == 0 { + backoff = time.Millisecond + } else { + backoff *= 2 + } + + backoff = min(time.Second, backoff) + + sleep := time.Duration(rand.Int63n(int64(backoff))) + log.G(ctx).WithError(err).Errorf("ttrpc: failed accept; backoff %v", sleep) + time.Sleep(sleep) + continue + } + + return err + } + + backoff = 0 + + approved, handshake, err := handshaker.Handshake(ctx, conn) + if err != nil { + log.G(ctx).WithError(err).Error("ttrpc: refusing connection after handshake") + conn.Close() + continue + } + + sc, err := s.newConn(approved, handshake) + if err != nil { + log.G(ctx).WithError(err).Error("ttrpc: create connection failed") + conn.Close() + continue + } + + go sc.run(ctx) + } +} + +func (s *Server) Shutdown(ctx context.Context) error { + s.mu.Lock() + select { + case <-s.done: + default: + // protected by mutex + close(s.done) + } + lnerr := s.closeListeners() + s.mu.Unlock() + + ticker := time.NewTicker(200 * time.Millisecond) + defer ticker.Stop() + for { + s.closeIdleConns() + + if s.countConnection() == 0 { + break + } + + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + } + + return lnerr +} + +// Close the server without waiting for active connections. +func (s *Server) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + + select { + case <-s.done: + default: + // protected by mutex + close(s.done) + } + + err := s.closeListeners() + for c := range s.connections { + c.close() + delete(s.connections, c) + } + + return err +} + +func (s *Server) addListenerLocked(l net.Listener) { + s.listeners[l] = struct{}{} +} + +func (s *Server) closeListener(l net.Listener) error { + s.mu.Lock() + defer s.mu.Unlock() + + return s.closeListenerLocked(l) +} + +func (s *Server) closeListenerLocked(l net.Listener) error { + defer delete(s.listeners, l) + return l.Close() +} + +func (s *Server) closeListeners() error { + var err error + for l := range s.listeners { + if cerr := s.closeListenerLocked(l); cerr != nil && err == nil { + err = cerr + } + } + return err +} + +func (s *Server) addConnection(c *serverConn) error { + s.mu.Lock() + defer s.mu.Unlock() + + select { + case <-s.done: + return ErrServerClosed + default: + } + + s.connections[c] = struct{}{} + return nil +} + +func (s *Server) delConnection(c *serverConn) { + s.mu.Lock() + defer s.mu.Unlock() + + delete(s.connections, c) +} + +func (s *Server) countConnection() int { + s.mu.Lock() + defer s.mu.Unlock() + + return len(s.connections) +} + +func (s *Server) closeIdleConns() { + s.mu.Lock() + defer s.mu.Unlock() + + for c := range s.connections { + if st, ok := c.getState(); !ok || st == connStateActive { + continue + } + c.close() + delete(s.connections, c) + } +} + +type connState int + +const ( + connStateActive = iota + 1 // outstanding requests + connStateIdle // no requests + connStateClosed // closed connection +) + +func (cs connState) String() string { + switch cs { + case connStateActive: + return "active" + case connStateIdle: + return "idle" + case connStateClosed: + return "closed" + default: + return "unknown" + } +} + +func (s *Server) newConn(conn net.Conn, handshake interface{}) (*serverConn, error) { + c := &serverConn{ + server: s, + conn: conn, + handshake: handshake, + shutdown: make(chan struct{}), + } + c.setState(connStateIdle) + if err := s.addConnection(c); err != nil { + c.close() + return nil, err + } + return c, nil +} + +type serverConn struct { + server *Server + conn net.Conn + handshake interface{} // data from handshake, not used for now + state atomic.Value + + shutdownOnce sync.Once + shutdown chan struct{} // forced shutdown, used by close +} + +func (c *serverConn) getState() (connState, bool) { + cs, ok := c.state.Load().(connState) + return cs, ok +} + +func (c *serverConn) setState(newstate connState) { + c.state.Store(newstate) +} + +func (c *serverConn) close() error { + c.shutdownOnce.Do(func() { + close(c.shutdown) + }) + + return nil +} + +func (c *serverConn) run(sctx context.Context) { + type ( + response struct { + id uint32 + status *status.Status + data []byte + closeStream bool + streaming bool + } + ) + + var ( + ch = newChannel(c.conn) + ctx, cancel = context.WithCancel(sctx) + state connState = connStateIdle + responses = make(chan response) + recvErr = make(chan error, 1) + done = make(chan struct{}) + streams = sync.Map{} + active int32 + lastStreamID uint32 + ) + + defer c.conn.Close() + defer cancel() + defer close(done) + defer c.server.delConnection(c) + + sendStatus := func(id uint32, st *status.Status) bool { + select { + case responses <- response{ + // even though we've had an invalid stream id, we send it + // back on the same stream id so the client knows which + // stream id was bad. + id: id, + status: st, + closeStream: true, + }: + return true + case <-c.shutdown: + return false + case <-done: + return false + } + } + + go func(recvErr chan error) { + defer close(recvErr) + for { + select { + case <-c.shutdown: + return + case <-done: + return + default: // proceed + } + + mh, p, err := ch.recv() + if err != nil { + status, ok := status.FromError(err) + if !ok { + recvErr <- err + return + } + + // in this case, we send an error for that particular message + // when the status is defined. + if !sendStatus(mh.StreamID, status) { + return + } + + continue + } + + if mh.StreamID%2 != 1 { + // enforce odd client initiated identifiers. + if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID must be odd for client initiated streams")) { + return + } + continue + } + + if mh.Type == messageTypeData { + i, ok := streams.Load(mh.StreamID) + if !ok { + if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID is no longer active")) { + return + } + continue + } + sh := i.(*streamHandler) + if mh.Flags&flagNoData != flagNoData { + unmarshal := func(obj interface{}) error { + err := protoUnmarshal(p, obj) + ch.putmbuf(p) + return err + } + + if err := sh.data(unmarshal); err != nil { + if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data handling error: %v", err)) { + return + } + continue + } + } + + if mh.Flags&flagRemoteClosed == flagRemoteClosed { + sh.closeSend() + if len(p) > 0 { + if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data close message cannot include data")) { + return + } + continue + } + } + } else if mh.Type == messageTypeRequest { + if mh.StreamID <= lastStreamID { + // enforce odd client initiated identifiers. + if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID cannot be re-used and must increment")) { + return + } + continue + + } + lastStreamID = mh.StreamID + + // TODO: Make request type configurable + // Unmarshaller which takes in a byte array and returns an interface? + var req Request + if err := c.server.codec.Unmarshal(p, &req); err != nil { + ch.putmbuf(p) + if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "unmarshal request error: %v", err)) { + return + } + continue + } + ch.putmbuf(p) + + id := mh.StreamID + respond := func(status *status.Status, data []byte, streaming, closeStream bool) error { + select { + case responses <- response{ + id: id, + status: status, + data: data, + closeStream: closeStream, + streaming: streaming, + }: + case <-done: + return ErrClosed + } + return nil + } + sh, err := c.server.services.handle(ctx, &req, respond) + if err != nil { + status, _ := status.FromError(err) + if !sendStatus(mh.StreamID, status) { + return + } + continue + } + + streams.Store(id, sh) + atomic.AddInt32(&active, 1) + } + // TODO: else we must ignore this for future compat. log this? + } + }(recvErr) + + for { + var ( + newstate connState + shutdown chan struct{} + ) + + activeN := atomic.LoadInt32(&active) + if activeN > 0 { + newstate = connStateActive + shutdown = nil + } else { + newstate = connStateIdle + shutdown = c.shutdown // only enable this branch in idle mode + } + if newstate != state { + c.setState(newstate) + state = newstate + } + + select { + case response := <-responses: + if !response.streaming || response.status.Code() != codes.OK { + p, err := c.server.codec.Marshal(&Response{ + Status: response.status.Proto(), + Payload: response.data, + }) + if err != nil { + log.G(ctx).WithError(err).Error("failed marshaling response") + return + } + + if err := ch.send(response.id, messageTypeResponse, 0, p); err != nil { + log.G(ctx).WithError(err).Error("failed sending message on channel") + return + } + } else { + var flags uint8 + if response.closeStream { + flags = flagRemoteClosed + } + if response.data == nil { + flags = flags | flagNoData + } + if err := ch.send(response.id, messageTypeData, flags, response.data); err != nil { + log.G(ctx).WithError(err).Error("failed sending message on channel") + return + } + } + + if response.closeStream { + // The ttrpc protocol currently does not support the case where + // the server is localClosed but not remoteClosed. Once the server + // is closing, the whole stream may be considered finished + streams.Delete(response.id) + atomic.AddInt32(&active, -1) + } + case err := <-recvErr: + // TODO(stevvooe): Not wildly clear what we should do in this + // branch. Basically, it means that we are no longer receiving + // requests due to a terminal error. + recvErr = nil // connection is now "closing" + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET) { + // The client went away and we should stop processing + // requests, so that the client connection is closed + return + } + log.G(ctx).WithError(err).Error("error receiving message") + // else, initiate shutdown + case <-shutdown: + return + } + } +} + +var noopFunc = func() {} + +func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) { + if len(req.Metadata) > 0 { + md := MD{} + md.fromRequest(req) + ctx = WithMetadata(ctx, md) + } + + cancel = noopFunc + if req.TimeoutNano == 0 { + return ctx, cancel + } + + ctx, cancel = context.WithTimeout(ctx, time.Duration(req.TimeoutNano)) + return ctx, cancel +} diff --git a/vendor/github.com/containerd/ttrpc/services.go b/vendor/github.com/containerd/ttrpc/services.go new file mode 100644 index 000000000..6d092bf95 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/services.go @@ -0,0 +1,279 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "path" + "unsafe" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +type Method func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) + +type StreamHandler func(context.Context, StreamServer) (interface{}, error) + +type Stream struct { + Handler StreamHandler + StreamingClient bool + StreamingServer bool +} + +type ServiceDesc struct { + Methods map[string]Method + Streams map[string]Stream +} + +type serviceSet struct { + services map[string]*ServiceDesc + unaryInterceptor UnaryServerInterceptor + streamInterceptor StreamServerInterceptor +} + +func newServiceSet(interceptor UnaryServerInterceptor) *serviceSet { + return &serviceSet{ + services: make(map[string]*ServiceDesc), + unaryInterceptor: interceptor, + streamInterceptor: defaultStreamServerInterceptor, + } +} + +func (s *serviceSet) register(name string, desc *ServiceDesc) { + if _, ok := s.services[name]; ok { + panic(fmt.Errorf("duplicate service %v registered", name)) + } + + s.services[name] = desc +} + +func (s *serviceSet) unaryCall(ctx context.Context, method Method, info *UnaryServerInfo, data []byte) (p []byte, st *status.Status) { + unmarshal := func(obj interface{}) error { + return protoUnmarshal(data, obj) + } + + resp, err := s.unaryInterceptor(ctx, unmarshal, info, method) + if err == nil { + if isNil(resp) { + err = errors.New("ttrpc: marshal called with nil") + } else { + p, err = protoMarshal(resp) + } + } + + st, ok := status.FromError(err) + if !ok { + st = status.New(convertCode(err), err.Error()) + } + + return p, st +} + +func (s *serviceSet) streamCall(ctx context.Context, stream StreamHandler, info *StreamServerInfo, ss StreamServer) (p []byte, st *status.Status) { + resp, err := s.streamInterceptor(ctx, ss, info, stream) + if err == nil { + p, err = protoMarshal(resp) + } + st, ok := status.FromError(err) + if !ok { + st = status.New(convertCode(err), err.Error()) + } + return +} + +func (s *serviceSet) handle(ctx context.Context, req *Request, respond func(*status.Status, []byte, bool, bool) error) (*streamHandler, error) { + srv, ok := s.services[req.Service] + if !ok { + return nil, status.Errorf(codes.Unimplemented, "service %v", req.Service) + } + + if method, ok := srv.Methods[req.Method]; ok { + go func() { + ctx, cancel := getRequestContext(ctx, req) + defer cancel() + + info := &UnaryServerInfo{ + FullMethod: fullPath(req.Service, req.Method), + } + p, st := s.unaryCall(ctx, method, info, req.Payload) + + respond(st, p, false, true) + }() + return nil, nil + } + if stream, ok := srv.Streams[req.Method]; ok { + ctx, cancel := getRequestContext(ctx, req) + info := &StreamServerInfo{ + FullMethod: fullPath(req.Service, req.Method), + StreamingClient: stream.StreamingClient, + StreamingServer: stream.StreamingServer, + } + sh := &streamHandler{ + ctx: ctx, + respond: respond, + recv: make(chan Unmarshaler, 5), + info: info, + } + go func() { + defer cancel() + p, st := s.streamCall(ctx, stream.Handler, info, sh) + respond(st, p, stream.StreamingServer, true) + }() + + // Empty proto messages serialized to 0 payloads, + // so signatures like: rpc Stream(google.protobuf.Empty) returns (stream Data); + // don't get invoked here, which causes hang on client side. + // See https://github.com/containerd/ttrpc/issues/126 + if req.Payload != nil || !info.StreamingClient { + unmarshal := func(obj interface{}) error { + return protoUnmarshal(req.Payload, obj) + } + if err := sh.data(unmarshal); err != nil { + return nil, err + } + } + + return sh, nil + } + return nil, status.Errorf(codes.Unimplemented, "method %v", req.Method) +} + +type streamHandler struct { + ctx context.Context + respond func(*status.Status, []byte, bool, bool) error + recv chan Unmarshaler + info *StreamServerInfo + + remoteClosed bool + localClosed bool +} + +func (s *streamHandler) closeSend() { + if !s.remoteClosed { + s.remoteClosed = true + close(s.recv) + } +} + +func (s *streamHandler) data(unmarshal Unmarshaler) error { + if s.remoteClosed { + return ErrStreamClosed + } + select { + case s.recv <- unmarshal: + return nil + case <-s.ctx.Done(): + return s.ctx.Err() + } +} + +func (s *streamHandler) SendMsg(m interface{}) error { + if s.localClosed { + return ErrStreamClosed + } + p, err := protoMarshal(m) + if err != nil { + return err + } + return s.respond(nil, p, true, false) +} + +func (s *streamHandler) RecvMsg(m interface{}) error { + select { + case unmarshal, ok := <-s.recv: + if !ok { + return io.EOF + } + return unmarshal(m) + case <-s.ctx.Done(): + return s.ctx.Err() + + } +} + +func protoUnmarshal(p []byte, obj interface{}) error { + switch v := obj.(type) { + case proto.Message: + if err := proto.Unmarshal(p, v); err != nil { + return status.Errorf(codes.Internal, "ttrpc: error unmarshalling payload: %v", err.Error()) + } + default: + return status.Errorf(codes.Internal, "ttrpc: error unsupported request type: %T", v) + } + return nil +} + +func protoMarshal(obj interface{}) ([]byte, error) { + if obj == nil { + return nil, nil + } + + switch v := obj.(type) { + case proto.Message: + r, err := proto.Marshal(v) + if err != nil { + return nil, status.Errorf(codes.Internal, "ttrpc: error marshaling payload: %v", err.Error()) + } + + return r, nil + default: + return nil, status.Errorf(codes.Internal, "ttrpc: error unsupported response type: %T", v) + } +} + +// convertCode maps stdlib go errors into grpc space. +// +// This is ripped from the grpc-go code base. +func convertCode(err error) codes.Code { + switch err { + case nil: + return codes.OK + case io.EOF: + return codes.OutOfRange + case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF: + return codes.FailedPrecondition + case os.ErrInvalid: + return codes.InvalidArgument + case context.Canceled: + return codes.Canceled + case context.DeadlineExceeded: + return codes.DeadlineExceeded + } + switch { + case os.IsExist(err): + return codes.AlreadyExists + case os.IsNotExist(err): + return codes.NotFound + case os.IsPermission(err): + return codes.PermissionDenied + } + return codes.Unknown +} + +func fullPath(service, method string) string { + return "/" + path.Join(service, method) +} + +func isNil(resp interface{}) bool { + return (*[2]uintptr)(unsafe.Pointer(&resp))[1] == 0 +} diff --git a/vendor/github.com/containerd/ttrpc/stream.go b/vendor/github.com/containerd/ttrpc/stream.go new file mode 100644 index 000000000..739a4c967 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/stream.go @@ -0,0 +1,84 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "sync" +) + +type streamID uint32 + +type streamMessage struct { + header messageHeader + payload []byte +} + +type stream struct { + id streamID + sender sender + recv chan *streamMessage + + closeOnce sync.Once + recvErr error + recvClose chan struct{} +} + +func newStream(id streamID, send sender) *stream { + return &stream{ + id: id, + sender: send, + recv: make(chan *streamMessage, 1), + recvClose: make(chan struct{}), + } +} + +func (s *stream) closeWithError(err error) error { + s.closeOnce.Do(func() { + if err != nil { + s.recvErr = err + } else { + s.recvErr = ErrClosed + } + close(s.recvClose) + }) + return nil +} + +func (s *stream) send(mt messageType, flags uint8, b []byte) error { + return s.sender.send(uint32(s.id), mt, flags, b) +} + +func (s *stream) receive(ctx context.Context, msg *streamMessage) error { + select { + case <-s.recvClose: + return s.recvErr + default: + } + select { + case <-s.recvClose: + return s.recvErr + case s.recv <- msg: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +type sender interface { + send(uint32, messageType, uint8, []byte) error +} diff --git a/vendor/github.com/containerd/ttrpc/stream_server.go b/vendor/github.com/containerd/ttrpc/stream_server.go new file mode 100644 index 000000000..b6d1ba720 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/stream_server.go @@ -0,0 +1,22 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +type StreamServer interface { + SendMsg(m interface{}) error + RecvMsg(m interface{}) error +} diff --git a/vendor/github.com/containerd/ttrpc/test.proto b/vendor/github.com/containerd/ttrpc/test.proto new file mode 100644 index 000000000..0e114d556 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/test.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package ttrpc; + +option go_package = "github.com/containerd/ttrpc/internal"; + +message TestPayload { + string foo = 1; + int64 deadline = 2; + string metadata = 3; +} + +message EchoPayload { + int64 seq = 1; + string msg = 2; +} diff --git a/vendor/github.com/containerd/ttrpc/unixcreds_linux.go b/vendor/github.com/containerd/ttrpc/unixcreds_linux.go new file mode 100644 index 000000000..c82c9f9d4 --- /dev/null +++ b/vendor/github.com/containerd/ttrpc/unixcreds_linux.go @@ -0,0 +1,105 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "errors" + "fmt" + "net" + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +type UnixCredentialsFunc func(*unix.Ucred) error + +func (fn UnixCredentialsFunc) Handshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) { + uc, err := requireUnixSocket(conn) + if err != nil { + return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: require unix socket: %w", err) + } + + rs, err := uc.SyscallConn() + if err != nil { + return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (net.UnixConn).SyscallConn failed: %w", err) + } + var ( + ucred *unix.Ucred + ucredErr error + ) + if err := rs.Control(func(fd uintptr) { + ucred, ucredErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED) + }); err != nil { + return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (*syscall.RawConn).Control failed: %w", err) + } + + if ucredErr != nil { + return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: failed to retrieve socket peer credentials: %w", ucredErr) + } + + if err := fn(ucred); err != nil { + return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: credential check failed: %w", err) + } + + return uc, ucred, nil +} + +// UnixSocketRequireUidGid requires specific *effective* UID/GID, rather than the real UID/GID. +// +// For example, if a daemon binary is owned by the root (UID 0) with SUID bit but running as an +// unprivileged user (UID 1001), the effective UID becomes 0, and the real UID becomes 1001. +// So calling this function with uid=0 allows a connection from effective UID 0 but rejects +// a connection from effective UID 1001. +// +// See socket(7), SO_PEERCRED: "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)." +func UnixSocketRequireUidGid(uid, gid int) UnixCredentialsFunc { + return func(ucred *unix.Ucred) error { + return requireUidGid(ucred, uid, gid) + } +} + +func UnixSocketRequireRoot() UnixCredentialsFunc { + return UnixSocketRequireUidGid(0, 0) +} + +// UnixSocketRequireSameUser resolves the current effective unix user and returns a +// UnixCredentialsFunc that will validate incoming unix connections against the +// current credentials. +// +// This is useful when using abstract sockets that are accessible by all users. +func UnixSocketRequireSameUser() UnixCredentialsFunc { + euid, egid := os.Geteuid(), os.Getegid() + return UnixSocketRequireUidGid(euid, egid) +} + +func requireUidGid(ucred *unix.Ucred, uid, gid int) error { + if (uid != -1 && uint32(uid) != ucred.Uid) || (gid != -1 && uint32(gid) != ucred.Gid) { + return fmt.Errorf("ttrpc: invalid credentials: %v", syscall.EPERM) + } + return nil +} + +func requireUnixSocket(conn net.Conn) (*net.UnixConn, error) { + uc, ok := conn.(*net.UnixConn) + if !ok { + return nil, errors.New("a unix socket connection is required") + } + + return uc, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1891f6f1d..0737efb53 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -241,10 +241,21 @@ github.com/cncf/xds/go/xds/data/orca/v3 github.com/cncf/xds/go/xds/service/orca/v3 github.com/cncf/xds/go/xds/type/matcher/v3 github.com/cncf/xds/go/xds/type/v3 +# github.com/containerd/containerd/api v1.11.1 +## explicit; go 1.24.0 +github.com/containerd/containerd/api/runtime/task/v2 +github.com/containerd/containerd/api/types +github.com/containerd/containerd/api/types/task +# github.com/containerd/log v0.1.0 +## explicit; go 1.20 +github.com/containerd/log # github.com/containerd/stargz-snapshotter/estargz v0.18.2 ## explicit; go 1.24.0 github.com/containerd/stargz-snapshotter/estargz github.com/containerd/stargz-snapshotter/estargz/errorutil +# github.com/containerd/ttrpc v1.2.8 +## explicit; go 1.22 +github.com/containerd/ttrpc # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew From c7ddb8996379055bd35212fdd972130e7ca1821a Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:08 -0700 Subject: [PATCH 02/14] proto: generalize runtime assets + snapshot file manifest - ateompb: runtime_asset_paths on Run/Checkpoint/Restore requests; CheckpointWorkloadResponse.snapshot_files so ateom reports exactly the files it wrote (CH snapshots differ from gVisor's fixed set). - ateletpb: RuntimeAsset/RuntimeAssetsConfig (runtime type, content-addressed asset map, authentication) on Run/Checkpoint/Restore requests. --- internal/proto/ateletpb/atelet.pb.go | 298 +++++++++++++++++++++------ internal/proto/ateletpb/atelet.proto | 26 +++ internal/proto/ateompb/ateom.pb.go | 118 ++++++++--- internal/proto/ateompb/ateom.proto | 23 ++- 4 files changed, 373 insertions(+), 92 deletions(-) diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index e3a55dd50..3a6d34c02 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -43,6 +43,7 @@ type RunRequest struct { ActorId string `protobuf:"bytes,5,opt,name=actor_id,json=actorId,proto3" json:"actor_id,omitempty"` Runsc *RunscConfig `protobuf:"bytes,8,opt,name=runsc,proto3" json:"runsc,omitempty"` Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` + RuntimeAssets *RuntimeAssetsConfig `protobuf:"bytes,9,opt,name=runtime_assets,json=runtimeAssets,proto3" json:"runtime_assets,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -119,6 +120,13 @@ func (x *RunRequest) GetSpec() *WorkloadSpec { return nil } +func (x *RunRequest) GetRuntimeAssets() *RuntimeAssetsConfig { + if x != nil { + return x.RuntimeAssets + } + return nil +} + type GCPAuthenticationConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Use bool `protobuf:"varint,1,opt,name=use,proto3" json:"use,omitempty"` @@ -323,6 +331,127 @@ func (x *RunscConfig) GetAuthentication() *AuthenticationConfig { return nil } +// RuntimeAsset is one content-addressed file atelet fetches for a non-gVisor +// runtime (e.g. the kata shim, cloud-hypervisor, virtiofsd, guest kernel/image, +// base configuration.toml). +type RuntimeAsset struct { + state protoimpl.MessageState `protogen:"open.v1"` + // sha256_hash both names the cached file and verifies its integrity. + Sha256Hash string `protobuf:"bytes,1,opt,name=sha256_hash,json=sha256Hash,proto3" json:"sha256_hash,omitempty"` + // gs:// URL to download from (resolved against the authenticated client when + // authentication is set, else the anonymous public client). + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RuntimeAsset) Reset() { + *x = RuntimeAsset{} + mi := &file_atelet_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RuntimeAsset) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuntimeAsset) ProtoMessage() {} + +func (x *RuntimeAsset) ProtoReflect() protoreflect.Message { + mi := &file_atelet_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RuntimeAsset.ProtoReflect.Descriptor instead. +func (*RuntimeAsset) Descriptor() ([]byte, []int) { + return file_atelet_proto_rawDescGZIP(), []int{5} +} + +func (x *RuntimeAsset) GetSha256Hash() string { + if x != nil { + return x.Sha256Hash + } + return "" +} + +func (x *RuntimeAsset) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +// RuntimeAssetsConfig describes the asset set for a non-gVisor runtime. atelet +// fetches each asset content-addressed (like runsc) and passes the local paths +// to ateom. Empty/runtime="gvisor" means use the runsc path instead. +type RuntimeAssetsConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Runtime string `protobuf:"bytes,1,opt,name=runtime,proto3" json:"runtime,omitempty"` // "gvisor" | "microvm" + Assets map[string]*RuntimeAsset `protobuf:"bytes,2,rep,name=assets,proto3" json:"assets,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // name -> asset + Authentication *AuthenticationConfig `protobuf:"bytes,3,opt,name=authentication,proto3" json:"authentication,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RuntimeAssetsConfig) Reset() { + *x = RuntimeAssetsConfig{} + mi := &file_atelet_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RuntimeAssetsConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuntimeAssetsConfig) ProtoMessage() {} + +func (x *RuntimeAssetsConfig) ProtoReflect() protoreflect.Message { + mi := &file_atelet_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RuntimeAssetsConfig.ProtoReflect.Descriptor instead. +func (*RuntimeAssetsConfig) Descriptor() ([]byte, []int) { + return file_atelet_proto_rawDescGZIP(), []int{6} +} + +func (x *RuntimeAssetsConfig) GetRuntime() string { + if x != nil { + return x.Runtime + } + return "" +} + +func (x *RuntimeAssetsConfig) GetAssets() map[string]*RuntimeAsset { + if x != nil { + return x.Assets + } + return nil +} + +func (x *RuntimeAssetsConfig) GetAuthentication() *AuthenticationConfig { + if x != nil { + return x.Authentication + } + return nil +} + // WorkloadSpec parallels Pod, but with far fewer configurable fields. type WorkloadSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -334,7 +463,7 @@ type WorkloadSpec struct { func (x *WorkloadSpec) Reset() { *x = WorkloadSpec{} - mi := &file_atelet_proto_msgTypes[5] + mi := &file_atelet_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -346,7 +475,7 @@ func (x *WorkloadSpec) String() string { func (*WorkloadSpec) ProtoMessage() {} func (x *WorkloadSpec) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[5] + mi := &file_atelet_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -359,7 +488,7 @@ func (x *WorkloadSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkloadSpec.ProtoReflect.Descriptor instead. func (*WorkloadSpec) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{5} + return file_atelet_proto_rawDescGZIP(), []int{7} } func (x *WorkloadSpec) GetContainers() []*Container { @@ -388,7 +517,7 @@ type Container struct { func (x *Container) Reset() { *x = Container{} - mi := &file_atelet_proto_msgTypes[6] + mi := &file_atelet_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -400,7 +529,7 @@ func (x *Container) String() string { func (*Container) ProtoMessage() {} func (x *Container) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[6] + mi := &file_atelet_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -413,7 +542,7 @@ func (x *Container) ProtoReflect() protoreflect.Message { // Deprecated: Use Container.ProtoReflect.Descriptor instead. func (*Container) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{6} + return file_atelet_proto_rawDescGZIP(), []int{8} } func (x *Container) GetName() string { @@ -454,7 +583,7 @@ type EnvEntry struct { func (x *EnvEntry) Reset() { *x = EnvEntry{} - mi := &file_atelet_proto_msgTypes[7] + mi := &file_atelet_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -466,7 +595,7 @@ func (x *EnvEntry) String() string { func (*EnvEntry) ProtoMessage() {} func (x *EnvEntry) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[7] + mi := &file_atelet_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -479,7 +608,7 @@ func (x *EnvEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use EnvEntry.ProtoReflect.Descriptor instead. func (*EnvEntry) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{7} + return file_atelet_proto_rawDescGZIP(), []int{9} } func (x *EnvEntry) GetName() string { @@ -504,7 +633,7 @@ type RunResponse struct { func (x *RunResponse) Reset() { *x = RunResponse{} - mi := &file_atelet_proto_msgTypes[8] + mi := &file_atelet_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -516,7 +645,7 @@ func (x *RunResponse) String() string { func (*RunResponse) ProtoMessage() {} func (x *RunResponse) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[8] + mi := &file_atelet_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -529,7 +658,7 @@ func (x *RunResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RunResponse.ProtoReflect.Descriptor instead. func (*RunResponse) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{8} + return file_atelet_proto_rawDescGZIP(), []int{10} } type CheckpointRequest struct { @@ -550,14 +679,15 @@ type CheckpointRequest struct { // can restore a checkpoint. // // For example: "gs://bucket/actors/1234/snapshots/5678/" - SnapshotUriPrefix string `protobuf:"bytes,8,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + SnapshotUriPrefix string `protobuf:"bytes,8,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + RuntimeAssets *RuntimeAssetsConfig `protobuf:"bytes,9,opt,name=runtime_assets,json=runtimeAssets,proto3" json:"runtime_assets,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CheckpointRequest) Reset() { *x = CheckpointRequest{} - mi := &file_atelet_proto_msgTypes[9] + mi := &file_atelet_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -569,7 +699,7 @@ func (x *CheckpointRequest) String() string { func (*CheckpointRequest) ProtoMessage() {} func (x *CheckpointRequest) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[9] + mi := &file_atelet_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -582,7 +712,7 @@ func (x *CheckpointRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckpointRequest.ProtoReflect.Descriptor instead. func (*CheckpointRequest) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{9} + return file_atelet_proto_rawDescGZIP(), []int{11} } func (x *CheckpointRequest) GetTargetAteomUid() string { @@ -634,6 +764,13 @@ func (x *CheckpointRequest) GetSnapshotUriPrefix() string { return "" } +func (x *CheckpointRequest) GetRuntimeAssets() *RuntimeAssetsConfig { + if x != nil { + return x.RuntimeAssets + } + return nil +} + type CheckpointResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -642,7 +779,7 @@ type CheckpointResponse struct { func (x *CheckpointResponse) Reset() { *x = CheckpointResponse{} - mi := &file_atelet_proto_msgTypes[10] + mi := &file_atelet_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -654,7 +791,7 @@ func (x *CheckpointResponse) String() string { func (*CheckpointResponse) ProtoMessage() {} func (x *CheckpointResponse) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[10] + mi := &file_atelet_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -667,7 +804,7 @@ func (x *CheckpointResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckpointResponse.ProtoReflect.Descriptor instead. func (*CheckpointResponse) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{10} + return file_atelet_proto_rawDescGZIP(), []int{12} } type RestoreRequest struct { @@ -679,14 +816,15 @@ type RestoreRequest struct { Runsc *RunscConfig `protobuf:"bytes,6,opt,name=runsc,proto3" json:"runsc,omitempty"` Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // The object storage URI prefix of the snapshot to restore. - SnapshotUriPrefix string `protobuf:"bytes,8,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + SnapshotUriPrefix string `protobuf:"bytes,8,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + RuntimeAssets *RuntimeAssetsConfig `protobuf:"bytes,9,opt,name=runtime_assets,json=runtimeAssets,proto3" json:"runtime_assets,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RestoreRequest) Reset() { *x = RestoreRequest{} - mi := &file_atelet_proto_msgTypes[11] + mi := &file_atelet_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -698,7 +836,7 @@ func (x *RestoreRequest) String() string { func (*RestoreRequest) ProtoMessage() {} func (x *RestoreRequest) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[11] + mi := &file_atelet_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -711,7 +849,7 @@ func (x *RestoreRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreRequest.ProtoReflect.Descriptor instead. func (*RestoreRequest) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{11} + return file_atelet_proto_rawDescGZIP(), []int{13} } func (x *RestoreRequest) GetTargetAteomUid() string { @@ -763,6 +901,13 @@ func (x *RestoreRequest) GetSnapshotUriPrefix() string { return "" } +func (x *RestoreRequest) GetRuntimeAssets() *RuntimeAssetsConfig { + if x != nil { + return x.RuntimeAssets + } + return nil +} + type RestoreResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -771,7 +916,7 @@ type RestoreResponse struct { func (x *RestoreResponse) Reset() { *x = RestoreResponse{} - mi := &file_atelet_proto_msgTypes[12] + mi := &file_atelet_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -783,7 +928,7 @@ func (x *RestoreResponse) String() string { func (*RestoreResponse) ProtoMessage() {} func (x *RestoreResponse) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[12] + mi := &file_atelet_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -796,14 +941,14 @@ func (x *RestoreResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreResponse.ProtoReflect.Descriptor instead. func (*RestoreResponse) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{12} + return file_atelet_proto_rawDescGZIP(), []int{14} } var File_atelet_proto protoreflect.FileDescriptor const file_atelet_proto_rawDesc = "" + "\n" + - "\fatelet.proto\x12\x06atelet\"\x90\x02\n" + + "\fatelet.proto\x12\x06atelet\"\xd4\x02\n" + "\n" + "RunRequest\x12(\n" + "\x10target_ateom_uid\x18\x01 \x01(\tR\x0etargetAteomUid\x128\n" + @@ -811,7 +956,8 @@ const file_atelet_proto_rawDesc = "" + "\x13actor_template_name\x18\x04 \x01(\tR\x11actorTemplateName\x12\x19\n" + "\bactor_id\x18\x05 \x01(\tR\aactorId\x12)\n" + "\x05runsc\x18\b \x01(\v2\x13.atelet.RunscConfigR\x05runsc\x12(\n" + - "\x04spec\x18\a \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\"+\n" + + "\x04spec\x18\a \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12B\n" + + "\x0eruntime_assets\x18\t \x01(\v2\x1b.atelet.RuntimeAssetsConfigR\rruntimeAssets\"+\n" + "\x17GCPAuthenticationConfig\x12\x10\n" + "\x03use\x18\x01 \x01(\bR\x03use\"I\n" + "\x14AuthenticationConfig\x121\n" + @@ -823,7 +969,18 @@ const file_atelet_proto_rawDesc = "" + "\vRunscConfig\x121\n" + "\x05amd64\x18\x01 \x01(\v2\x1b.atelet.RunscPlatformConfigR\x05amd64\x121\n" + "\x05arm64\x18\x02 \x01(\v2\x1b.atelet.RunscPlatformConfigR\x05arm64\x12D\n" + - "\x0eauthentication\x18\x03 \x01(\v2\x1c.atelet.AuthenticationConfigR\x0eauthentication\"b\n" + + "\x0eauthentication\x18\x03 \x01(\v2\x1c.atelet.AuthenticationConfigR\x0eauthentication\"A\n" + + "\fRuntimeAsset\x12\x1f\n" + + "\vsha256_hash\x18\x01 \x01(\tR\n" + + "sha256Hash\x12\x10\n" + + "\x03url\x18\x02 \x01(\tR\x03url\"\x87\x02\n" + + "\x13RuntimeAssetsConfig\x12\x18\n" + + "\aruntime\x18\x01 \x01(\tR\aruntime\x12?\n" + + "\x06assets\x18\x02 \x03(\v2'.atelet.RuntimeAssetsConfig.AssetsEntryR\x06assets\x12D\n" + + "\x0eauthentication\x18\x03 \x01(\v2\x1c.atelet.AuthenticationConfigR\x0eauthentication\x1aO\n" + + "\vAssetsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12*\n" + + "\x05value\x18\x02 \x01(\v2\x14.atelet.RuntimeAssetR\x05value:\x028\x01\"b\n" + "\fWorkloadSpec\x121\n" + "\n" + "containers\x18\x01 \x03(\v2\x11.atelet.ContainerR\n" + @@ -838,7 +995,7 @@ const file_atelet_proto_rawDesc = "" + "\bEnvEntry\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value\"\r\n" + - "\vRunResponse\"\xc7\x02\n" + + "\vRunResponse\"\x8b\x03\n" + "\x11CheckpointRequest\x12(\n" + "\x10target_ateom_uid\x18\x01 \x01(\tR\x0etargetAteomUid\x128\n" + "\x18actor_template_namespace\x18\x03 \x01(\tR\x16actorTemplateNamespace\x12.\n" + @@ -846,8 +1003,9 @@ const file_atelet_proto_rawDesc = "" + "\bactor_id\x18\x05 \x01(\tR\aactorId\x12)\n" + "\x05runsc\x18\x06 \x01(\v2\x13.atelet.RunscConfigR\x05runsc\x12(\n" + "\x04spec\x18\a \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12.\n" + - "\x13snapshot_uri_prefix\x18\b \x01(\tR\x11snapshotUriPrefix\"\x14\n" + - "\x12CheckpointResponse\"\xc4\x02\n" + + "\x13snapshot_uri_prefix\x18\b \x01(\tR\x11snapshotUriPrefix\x12B\n" + + "\x0eruntime_assets\x18\t \x01(\v2\x1b.atelet.RuntimeAssetsConfigR\rruntimeAssets\"\x14\n" + + "\x12CheckpointResponse\"\x88\x03\n" + "\x0eRestoreRequest\x12(\n" + "\x10target_ateom_uid\x18\x01 \x01(\tR\x0etargetAteomUid\x128\n" + "\x18actor_template_namespace\x18\x03 \x01(\tR\x16actorTemplateNamespace\x12.\n" + @@ -855,7 +1013,8 @@ const file_atelet_proto_rawDesc = "" + "\bactor_id\x18\x05 \x01(\tR\aactorId\x12)\n" + "\x05runsc\x18\x06 \x01(\v2\x13.atelet.RunscConfigR\x05runsc\x12(\n" + "\x04spec\x18\a \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12.\n" + - "\x13snapshot_uri_prefix\x18\b \x01(\tR\x11snapshotUriPrefix\"\x11\n" + + "\x13snapshot_uri_prefix\x18\b \x01(\tR\x11snapshotUriPrefix\x12B\n" + + "\x0eruntime_assets\x18\t \x01(\v2\x1b.atelet.RuntimeAssetsConfigR\rruntimeAssets\"\x11\n" + "\x0fRestoreResponse2\xc4\x01\n" + "\vAteomHerder\x120\n" + "\x03Run\x12\x12.atelet.RunRequest\x1a\x13.atelet.RunResponse\"\x00\x12E\n" + @@ -875,46 +1034,55 @@ func file_atelet_proto_rawDescGZIP() []byte { return file_atelet_proto_rawDescData } -var file_atelet_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_atelet_proto_msgTypes = make([]protoimpl.MessageInfo, 16) var file_atelet_proto_goTypes = []any{ (*RunRequest)(nil), // 0: atelet.RunRequest (*GCPAuthenticationConfig)(nil), // 1: atelet.GCPAuthenticationConfig (*AuthenticationConfig)(nil), // 2: atelet.AuthenticationConfig (*RunscPlatformConfig)(nil), // 3: atelet.RunscPlatformConfig (*RunscConfig)(nil), // 4: atelet.RunscConfig - (*WorkloadSpec)(nil), // 5: atelet.WorkloadSpec - (*Container)(nil), // 6: atelet.Container - (*EnvEntry)(nil), // 7: atelet.EnvEntry - (*RunResponse)(nil), // 8: atelet.RunResponse - (*CheckpointRequest)(nil), // 9: atelet.CheckpointRequest - (*CheckpointResponse)(nil), // 10: atelet.CheckpointResponse - (*RestoreRequest)(nil), // 11: atelet.RestoreRequest - (*RestoreResponse)(nil), // 12: atelet.RestoreResponse + (*RuntimeAsset)(nil), // 5: atelet.RuntimeAsset + (*RuntimeAssetsConfig)(nil), // 6: atelet.RuntimeAssetsConfig + (*WorkloadSpec)(nil), // 7: atelet.WorkloadSpec + (*Container)(nil), // 8: atelet.Container + (*EnvEntry)(nil), // 9: atelet.EnvEntry + (*RunResponse)(nil), // 10: atelet.RunResponse + (*CheckpointRequest)(nil), // 11: atelet.CheckpointRequest + (*CheckpointResponse)(nil), // 12: atelet.CheckpointResponse + (*RestoreRequest)(nil), // 13: atelet.RestoreRequest + (*RestoreResponse)(nil), // 14: atelet.RestoreResponse + nil, // 15: atelet.RuntimeAssetsConfig.AssetsEntry } var file_atelet_proto_depIdxs = []int32{ 4, // 0: atelet.RunRequest.runsc:type_name -> atelet.RunscConfig - 5, // 1: atelet.RunRequest.spec:type_name -> atelet.WorkloadSpec - 1, // 2: atelet.AuthenticationConfig.gcp:type_name -> atelet.GCPAuthenticationConfig - 3, // 3: atelet.RunscConfig.amd64:type_name -> atelet.RunscPlatformConfig - 3, // 4: atelet.RunscConfig.arm64:type_name -> atelet.RunscPlatformConfig - 2, // 5: atelet.RunscConfig.authentication:type_name -> atelet.AuthenticationConfig - 6, // 6: atelet.WorkloadSpec.containers:type_name -> atelet.Container - 7, // 7: atelet.Container.env:type_name -> atelet.EnvEntry - 4, // 8: atelet.CheckpointRequest.runsc:type_name -> atelet.RunscConfig - 5, // 9: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec - 4, // 10: atelet.RestoreRequest.runsc:type_name -> atelet.RunscConfig - 5, // 11: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec - 0, // 12: atelet.AteomHerder.Run:input_type -> atelet.RunRequest - 9, // 13: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest - 11, // 14: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest - 8, // 15: atelet.AteomHerder.Run:output_type -> atelet.RunResponse - 10, // 16: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse - 12, // 17: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse - 15, // [15:18] is the sub-list for method output_type - 12, // [12:15] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 7, // 1: atelet.RunRequest.spec:type_name -> atelet.WorkloadSpec + 6, // 2: atelet.RunRequest.runtime_assets:type_name -> atelet.RuntimeAssetsConfig + 1, // 3: atelet.AuthenticationConfig.gcp:type_name -> atelet.GCPAuthenticationConfig + 3, // 4: atelet.RunscConfig.amd64:type_name -> atelet.RunscPlatformConfig + 3, // 5: atelet.RunscConfig.arm64:type_name -> atelet.RunscPlatformConfig + 2, // 6: atelet.RunscConfig.authentication:type_name -> atelet.AuthenticationConfig + 15, // 7: atelet.RuntimeAssetsConfig.assets:type_name -> atelet.RuntimeAssetsConfig.AssetsEntry + 2, // 8: atelet.RuntimeAssetsConfig.authentication:type_name -> atelet.AuthenticationConfig + 8, // 9: atelet.WorkloadSpec.containers:type_name -> atelet.Container + 9, // 10: atelet.Container.env:type_name -> atelet.EnvEntry + 4, // 11: atelet.CheckpointRequest.runsc:type_name -> atelet.RunscConfig + 7, // 12: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec + 6, // 13: atelet.CheckpointRequest.runtime_assets:type_name -> atelet.RuntimeAssetsConfig + 4, // 14: atelet.RestoreRequest.runsc:type_name -> atelet.RunscConfig + 7, // 15: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec + 6, // 16: atelet.RestoreRequest.runtime_assets:type_name -> atelet.RuntimeAssetsConfig + 5, // 17: atelet.RuntimeAssetsConfig.AssetsEntry.value:type_name -> atelet.RuntimeAsset + 0, // 18: atelet.AteomHerder.Run:input_type -> atelet.RunRequest + 11, // 19: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest + 13, // 20: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest + 10, // 21: atelet.AteomHerder.Run:output_type -> atelet.RunResponse + 12, // 22: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse + 14, // 23: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse + 21, // [21:24] is the sub-list for method output_type + 18, // [18:21] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_atelet_proto_init() } @@ -928,7 +1096,7 @@ func file_atelet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_atelet_proto_rawDesc), len(file_atelet_proto_rawDesc)), NumEnums: 0, - NumMessages: 13, + NumMessages: 16, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index 792cffc98..016bd1624 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -42,6 +42,8 @@ message RunRequest { RunscConfig runsc = 8; WorkloadSpec spec = 7; + RuntimeAssetsConfig runtime_assets = 9; + } message GCPAuthenticationConfig { @@ -69,6 +71,26 @@ message RunscConfig { AuthenticationConfig authentication = 3; } +// RuntimeAsset is one content-addressed file atelet fetches for a non-gVisor +// runtime (e.g. the kata shim, cloud-hypervisor, virtiofsd, guest kernel/image, +// base configuration.toml). +message RuntimeAsset { + // sha256_hash both names the cached file and verifies its integrity. + string sha256_hash = 1; + // gs:// URL to download from (resolved against the authenticated client when + // authentication is set, else the anonymous public client). + string url = 2; +} + +// RuntimeAssetsConfig describes the asset set for a non-gVisor runtime. atelet +// fetches each asset content-addressed (like runsc) and passes the local paths +// to ateom. Empty/runtime="gvisor" means use the runsc path instead. +message RuntimeAssetsConfig { + string runtime = 1; // "gvisor" | "microvm" + map assets = 2; // name -> asset + AuthenticationConfig authentication = 3; +} + // WorkloadSpec parallels Pod, but with far fewer configurable fields. message WorkloadSpec { repeated Container containers = 1; @@ -112,6 +134,8 @@ message CheckpointRequest { // // For example: "gs://bucket/actors/1234/snapshots/5678/" string snapshot_uri_prefix = 8; + RuntimeAssetsConfig runtime_assets = 9; + } message CheckpointResponse { @@ -131,6 +155,8 @@ message RestoreRequest { // The object storage URI prefix of the snapshot to restore. string snapshot_uri_prefix = 8; + RuntimeAssetsConfig runtime_assets = 9; + } message RestoreResponse { diff --git a/internal/proto/ateompb/ateom.pb.go b/internal/proto/ateompb/ateom.pb.go index 9114a309b..d5040b7fb 100644 --- a/internal/proto/ateompb/ateom.pb.go +++ b/internal/proto/ateompb/ateom.pb.go @@ -42,8 +42,13 @@ type RunWorkloadRequest struct { ActorId string `protobuf:"bytes,3,opt,name=actor_id,json=actorId,proto3" json:"actor_id,omitempty"` RunscPath string `protobuf:"bytes,4,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` Spec *WorkloadSpec `protobuf:"bytes,5,opt,name=spec,proto3" json:"spec,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // runtime_asset_paths maps a runtime asset name (e.g. "kata-shim", + // "cloud-hypervisor", "virtiofsd", "kata-kernel", "kata-image", "kata-config") + // to the local on-disk path atelet fetched it to (content-addressed, like + // runsc_path). Empty for the gVisor runtime, which uses runsc_path. + RuntimeAssetPaths map[string]string `protobuf:"bytes,7,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RunWorkloadRequest) Reset() { @@ -111,6 +116,13 @@ func (x *RunWorkloadRequest) GetSpec() *WorkloadSpec { return nil } +func (x *RunWorkloadRequest) GetRuntimeAssetPaths() map[string]string { + if x != nil { + return x.RuntimeAssetPaths + } + return nil +} + // WorkloadSpec parallels Pod, but with far fewer configurable fields. type WorkloadSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -252,6 +264,11 @@ type CheckpointWorkloadRequest struct { // // For example: "gs://bucket/actors/1234/snapshots/5678/" SnapshotUriPrefix string `protobuf:"bytes,6,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + // runtime_asset_paths maps a runtime asset name (e.g. "kata-shim", + // "cloud-hypervisor", "virtiofsd", "kata-kernel", "kata-image", "kata-config") + // to the local on-disk path atelet fetched it to (content-addressed, like + // runsc_path). Empty for the gVisor runtime, which uses runsc_path. + RuntimeAssetPaths map[string]string `protobuf:"bytes,7,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -328,8 +345,19 @@ func (x *CheckpointWorkloadRequest) GetSnapshotUriPrefix() string { return "" } +func (x *CheckpointWorkloadRequest) GetRuntimeAssetPaths() map[string]string { + if x != nil { + return x.RuntimeAssetPaths + } + return nil +} + type CheckpointWorkloadResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState `protogen:"open.v1"` + // snapshot_files lists the files ateom wrote into the checkpoint directory + // (relative names) for atelet to ship to object storage. Empty for runtimes + // (e.g. gVisor) that use atelet's legacy fixed file set. + SnapshotFiles []string `protobuf:"bytes,1,rep,name=snapshot_files,json=snapshotFiles,proto3" json:"snapshot_files,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -364,6 +392,13 @@ func (*CheckpointWorkloadResponse) Descriptor() ([]byte, []int) { return file_ateom_proto_rawDescGZIP(), []int{5} } +func (x *CheckpointWorkloadResponse) GetSnapshotFiles() []string { + if x != nil { + return x.SnapshotFiles + } + return nil +} + type RestoreWorkloadRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ActorTemplateNamespace string `protobuf:"bytes,1,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` @@ -373,6 +408,11 @@ type RestoreWorkloadRequest struct { Spec *WorkloadSpec `protobuf:"bytes,5,opt,name=spec,proto3" json:"spec,omitempty"` // The object storage URI prefix of the snapshot to restore. SnapshotUriPrefix string `protobuf:"bytes,6,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + // runtime_asset_paths maps a runtime asset name (e.g. "kata-shim", + // "cloud-hypervisor", "virtiofsd", "kata-kernel", "kata-image", "kata-config") + // to the local on-disk path atelet fetched it to (content-addressed, like + // runsc_path). Empty for the gVisor runtime, which uses runsc_path. + RuntimeAssetPaths map[string]string `protobuf:"bytes,7,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -449,6 +489,13 @@ func (x *RestoreWorkloadRequest) GetSnapshotUriPrefix() string { return "" } +func (x *RestoreWorkloadRequest) GetRuntimeAssetPaths() map[string]string { + if x != nil { + return x.RuntimeAssetPaths + } + return nil +} + type RestoreWorkloadResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -489,21 +536,25 @@ var File_ateom_proto protoreflect.FileDescriptor const file_ateom_proto_rawDesc = "" + "\n" + - "\vateom.proto\x12\x05ateom\"\xe1\x01\n" + + "\vateom.proto\x12\x05ateom\"\x89\x03\n" + "\x12RunWorkloadRequest\x128\n" + "\x18actor_template_namespace\x18\x01 \x01(\tR\x16actorTemplateNamespace\x12.\n" + "\x13actor_template_name\x18\x02 \x01(\tR\x11actorTemplateName\x12\x19\n" + "\bactor_id\x18\x03 \x01(\tR\aactorId\x12\x1d\n" + "\n" + "runsc_path\x18\x04 \x01(\tR\trunscPath\x12'\n" + - "\x04spec\x18\x05 \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\"@\n" + + "\x04spec\x18\x05 \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12`\n" + + "\x13runtime_asset_paths\x18\a \x03(\v20.ateom.RunWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x1aD\n" + + "\x16RuntimeAssetPathsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"@\n" + "\fWorkloadSpec\x120\n" + "\n" + "containers\x18\x01 \x03(\v2\x10.ateom.ContainerR\n" + "containers\"\x1f\n" + "\tContainer\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\"\x15\n" + - "\x13RunWorkloadResponse\"\x98\x02\n" + + "\x13RunWorkloadResponse\"\xc7\x03\n" + "\x19CheckpointWorkloadRequest\x128\n" + "\x18actor_template_namespace\x18\x01 \x01(\tR\x16actorTemplateNamespace\x12.\n" + "\x13actor_template_name\x18\x02 \x01(\tR\x11actorTemplateName\x12\x19\n" + @@ -511,8 +562,13 @@ const file_ateom_proto_rawDesc = "" + "\n" + "runsc_path\x18\x04 \x01(\tR\trunscPath\x12'\n" + "\x04spec\x18\x05 \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12.\n" + - "\x13snapshot_uri_prefix\x18\x06 \x01(\tR\x11snapshotUriPrefix\"\x1c\n" + - "\x1aCheckpointWorkloadResponse\"\x95\x02\n" + + "\x13snapshot_uri_prefix\x18\x06 \x01(\tR\x11snapshotUriPrefix\x12g\n" + + "\x13runtime_asset_paths\x18\a \x03(\v27.ateom.CheckpointWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x1aD\n" + + "\x16RuntimeAssetPathsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"C\n" + + "\x1aCheckpointWorkloadResponse\x12%\n" + + "\x0esnapshot_files\x18\x01 \x03(\tR\rsnapshotFiles\"\xc1\x03\n" + "\x16RestoreWorkloadRequest\x128\n" + "\x18actor_template_namespace\x18\x01 \x01(\tR\x16actorTemplateNamespace\x12.\n" + "\x13actor_template_name\x18\x02 \x01(\tR\x11actorTemplateName\x12\x19\n" + @@ -520,7 +576,11 @@ const file_ateom_proto_rawDesc = "" + "\n" + "runsc_path\x18\x04 \x01(\tR\trunscPath\x12'\n" + "\x04spec\x18\x05 \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12.\n" + - "\x13snapshot_uri_prefix\x18\x06 \x01(\tR\x11snapshotUriPrefix\"\x19\n" + + "\x13snapshot_uri_prefix\x18\x06 \x01(\tR\x11snapshotUriPrefix\x12d\n" + + "\x13runtime_asset_paths\x18\a \x03(\v24.ateom.RestoreWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x1aD\n" + + "\x16RuntimeAssetPathsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x19\n" + "\x17RestoreWorkloadResponse2\x80\x02\n" + "\x05Ateom\x12F\n" + "\vRunWorkload\x12\x19.ateom.RunWorkloadRequest\x1a\x1a.ateom.RunWorkloadResponse\"\x00\x12[\n" + @@ -539,7 +599,7 @@ func file_ateom_proto_rawDescGZIP() []byte { return file_ateom_proto_rawDescData } -var file_ateom_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_ateom_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_ateom_proto_goTypes = []any{ (*RunWorkloadRequest)(nil), // 0: ateom.RunWorkloadRequest (*WorkloadSpec)(nil), // 1: ateom.WorkloadSpec @@ -549,23 +609,29 @@ var file_ateom_proto_goTypes = []any{ (*CheckpointWorkloadResponse)(nil), // 5: ateom.CheckpointWorkloadResponse (*RestoreWorkloadRequest)(nil), // 6: ateom.RestoreWorkloadRequest (*RestoreWorkloadResponse)(nil), // 7: ateom.RestoreWorkloadResponse + nil, // 8: ateom.RunWorkloadRequest.RuntimeAssetPathsEntry + nil, // 9: ateom.CheckpointWorkloadRequest.RuntimeAssetPathsEntry + nil, // 10: ateom.RestoreWorkloadRequest.RuntimeAssetPathsEntry } var file_ateom_proto_depIdxs = []int32{ - 1, // 0: ateom.RunWorkloadRequest.spec:type_name -> ateom.WorkloadSpec - 2, // 1: ateom.WorkloadSpec.containers:type_name -> ateom.Container - 1, // 2: ateom.CheckpointWorkloadRequest.spec:type_name -> ateom.WorkloadSpec - 1, // 3: ateom.RestoreWorkloadRequest.spec:type_name -> ateom.WorkloadSpec - 0, // 4: ateom.Ateom.RunWorkload:input_type -> ateom.RunWorkloadRequest - 4, // 5: ateom.Ateom.CheckpointWorkload:input_type -> ateom.CheckpointWorkloadRequest - 6, // 6: ateom.Ateom.RestoreWorkload:input_type -> ateom.RestoreWorkloadRequest - 3, // 7: ateom.Ateom.RunWorkload:output_type -> ateom.RunWorkloadResponse - 5, // 8: ateom.Ateom.CheckpointWorkload:output_type -> ateom.CheckpointWorkloadResponse - 7, // 9: ateom.Ateom.RestoreWorkload:output_type -> ateom.RestoreWorkloadResponse - 7, // [7:10] is the sub-list for method output_type - 4, // [4:7] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 1, // 0: ateom.RunWorkloadRequest.spec:type_name -> ateom.WorkloadSpec + 8, // 1: ateom.RunWorkloadRequest.runtime_asset_paths:type_name -> ateom.RunWorkloadRequest.RuntimeAssetPathsEntry + 2, // 2: ateom.WorkloadSpec.containers:type_name -> ateom.Container + 1, // 3: ateom.CheckpointWorkloadRequest.spec:type_name -> ateom.WorkloadSpec + 9, // 4: ateom.CheckpointWorkloadRequest.runtime_asset_paths:type_name -> ateom.CheckpointWorkloadRequest.RuntimeAssetPathsEntry + 1, // 5: ateom.RestoreWorkloadRequest.spec:type_name -> ateom.WorkloadSpec + 10, // 6: ateom.RestoreWorkloadRequest.runtime_asset_paths:type_name -> ateom.RestoreWorkloadRequest.RuntimeAssetPathsEntry + 0, // 7: ateom.Ateom.RunWorkload:input_type -> ateom.RunWorkloadRequest + 4, // 8: ateom.Ateom.CheckpointWorkload:input_type -> ateom.CheckpointWorkloadRequest + 6, // 9: ateom.Ateom.RestoreWorkload:input_type -> ateom.RestoreWorkloadRequest + 3, // 10: ateom.Ateom.RunWorkload:output_type -> ateom.RunWorkloadResponse + 5, // 11: ateom.Ateom.CheckpointWorkload:output_type -> ateom.CheckpointWorkloadResponse + 7, // 12: ateom.Ateom.RestoreWorkload:output_type -> ateom.RestoreWorkloadResponse + 10, // [10:13] is the sub-list for method output_type + 7, // [7:10] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_ateom_proto_init() } @@ -579,7 +645,7 @@ func file_ateom_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ateom_proto_rawDesc), len(file_ateom_proto_rawDesc)), NumEnums: 0, - NumMessages: 8, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/proto/ateompb/ateom.proto b/internal/proto/ateompb/ateom.proto index 1da19e5a3..f88497065 100644 --- a/internal/proto/ateompb/ateom.proto +++ b/internal/proto/ateompb/ateom.proto @@ -55,6 +55,12 @@ message RunWorkloadRequest { string runsc_path = 4; WorkloadSpec spec = 5; + // runtime_asset_paths maps a runtime asset name (e.g. "kata-shim", + // "cloud-hypervisor", "virtiofsd", "kata-kernel", "kata-image", "kata-config") + // to the local on-disk path atelet fetched it to (content-addressed, like + // runsc_path). Empty for the gVisor runtime, which uses runsc_path. + map runtime_asset_paths = 7; + } // WorkloadSpec parallels Pod, but with far fewer configurable fields. @@ -87,10 +93,19 @@ message CheckpointWorkloadRequest { // // For example: "gs://bucket/actors/1234/snapshots/5678/" string snapshot_uri_prefix = 6; + // runtime_asset_paths maps a runtime asset name (e.g. "kata-shim", + // "cloud-hypervisor", "virtiofsd", "kata-kernel", "kata-image", "kata-config") + // to the local on-disk path atelet fetched it to (content-addressed, like + // runsc_path). Empty for the gVisor runtime, which uses runsc_path. + map runtime_asset_paths = 7; + } message CheckpointWorkloadResponse { - + // snapshot_files lists the files ateom wrote into the checkpoint directory + // (relative names) for atelet to ship to object storage. Empty for runtimes + // (e.g. gVisor) that use atelet's legacy fixed file set. + repeated string snapshot_files = 1; } message RestoreWorkloadRequest { @@ -104,6 +119,12 @@ message RestoreWorkloadRequest { // The object storage URI prefix of the snapshot to restore. string snapshot_uri_prefix = 6; + // runtime_asset_paths maps a runtime asset name (e.g. "kata-shim", + // "cloud-hypervisor", "virtiofsd", "kata-kernel", "kata-image", "kata-config") + // to the local on-disk path atelet fetched it to (content-addressed, like + // runsc_path). Empty for the gVisor runtime, which uses runsc_path. + map runtime_asset_paths = 7; + } message RestoreWorkloadResponse { From bc98140680faf04c82cc3e10621f11a6d5c1c392 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:08 -0700 Subject: [PATCH 03/14] api: WorkerPool runtime enum + ActorTemplate runtime config WorkerPool.spec.runtime (gvisor|microvm, default gvisor) drives worker pod shape; ActorTemplate.spec.runtime carries the runtime type plus the content-addressed asset set (URL + sha256) atelet fetches at runtime, with optional authentication. CRDs + deepcopy regenerated. --- .../generated/ate.dev_actortemplates.yaml | 42 +++++++++++++++++++ .../generated/ate.dev_workerpools.yaml | 10 +++++ pkg/api/v1alpha1/actortemplate_types.go | 38 +++++++++++++++++ pkg/api/v1alpha1/workerpool_types.go | 21 ++++++++++ pkg/api/v1alpha1/zz_generated.deepcopy.go | 39 +++++++++++++++++ 5 files changed, 150 insertions(+) diff --git a/manifests/ate-install/generated/ate.dev_actortemplates.yaml b/manifests/ate-install/generated/ate.dev_actortemplates.yaml index 5f8bd893d..5b1ca8764 100644 --- a/manifests/ate-install/generated/ate.dev_actortemplates.yaml +++ b/manifests/ate-install/generated/ate.dev_actortemplates.yaml @@ -218,6 +218,48 @@ spec: type: object type: object type: object + runtime: + description: |- + Runtime configures a non-gVisor sandbox runtime (e.g. micro-VM). When + Type=microvm, atelet fetches Assets (kata shim, cloud-hypervisor, + virtiofsd, guest kernel/image, base config) instead of runsc. + properties: + assets: + additionalProperties: + description: |- + RuntimeAsset is one content-addressed file atelet fetches for a non-gVisor + runtime. + properties: + sha256Hash: + description: The SHA256 hash of the asset; names the cached + file and verifies integrity. + type: string + url: + description: |- + A gs:// URL pointing to the asset (resolved against the authenticated + client when authentication is set, else the anonymous public client). + type: string + required: + - sha256Hash + - url + type: object + description: Assets maps an asset name to its download config. + type: object + authentication: + description: How should atelet authenticate to fetch the assets? + properties: + gcp: + description: Use GCP application-default credentials. + type: object + type: object + type: + description: Type selects the runtime family. Empty/"gvisor" uses + the runsc path. + enum: + - gvisor + - microvm + type: string + type: object snapshotsConfig: description: Snapshots configuration for the actor. properties: diff --git a/manifests/ate-install/generated/ate.dev_workerpools.yaml b/manifests/ate-install/generated/ate.dev_workerpools.yaml index 634512b23..0e7548f08 100644 --- a/manifests/ate-install/generated/ate.dev_workerpools.yaml +++ b/manifests/ate-install/generated/ate.dev_workerpools.yaml @@ -75,6 +75,16 @@ spec: format: int32 minimum: 0 type: integer + runtime: + default: gvisor + description: |- + Runtime selects the sandbox runtime family, which drives the worker pod + shape (KVM/vhost device mounts and node placement). The concrete binary is + still selected by AteomImage. Defaults to gvisor. + enum: + - gvisor + - microvm + type: string required: - ateomImage - replicas diff --git a/pkg/api/v1alpha1/actortemplate_types.go b/pkg/api/v1alpha1/actortemplate_types.go index 7c1272ca9..b07b4092c 100644 --- a/pkg/api/v1alpha1/actortemplate_types.go +++ b/pkg/api/v1alpha1/actortemplate_types.go @@ -168,6 +168,44 @@ type ActorTemplateSpec struct { // // +required Runsc RunscConfig `json:"runsc,omitempty"` + + // Runtime configures a non-gVisor sandbox runtime (e.g. micro-VM). When + // Type=microvm, atelet fetches Assets (kata shim, cloud-hypervisor, + // virtiofsd, guest kernel/image, base config) instead of runsc. + // + // +optional + Runtime RuntimeConfig `json:"runtime,omitempty"` +} + +// RuntimeAsset is one content-addressed file atelet fetches for a non-gVisor +// runtime. +type RuntimeAsset struct { + // The SHA256 hash of the asset; names the cached file and verifies integrity. + // +required + SHA256Hash string `json:"sha256Hash,omitempty"` + + // A gs:// URL pointing to the asset (resolved against the authenticated + // client when authentication is set, else the anonymous public client). + // +required + URL string `json:"url,omitempty"` +} + +// RuntimeConfig configures a non-gVisor sandbox runtime: the asset set atelet +// fetches, keyed by name (e.g. "kata-shim", "cloud-hypervisor", "virtiofsd", +// "kata-kernel", "kata-image", "kata-config"). +type RuntimeConfig struct { + // Type selects the runtime family. Empty/"gvisor" uses the runsc path. + // +optional + // +kubebuilder:validation:Enum=gvisor;microvm + Type string `json:"type,omitempty"` + + // Assets maps an asset name to its download config. + // +optional + Assets map[string]RuntimeAsset `json:"assets,omitempty"` + + // How should atelet authenticate to fetch the assets? + // +optional + Authentication AuthenticationConfig `json:"authentication,omitempty"` } type GCPAuthenticationConfig struct { diff --git a/pkg/api/v1alpha1/workerpool_types.go b/pkg/api/v1alpha1/workerpool_types.go index 2350432b6..0e011daee 100644 --- a/pkg/api/v1alpha1/workerpool_types.go +++ b/pkg/api/v1alpha1/workerpool_types.go @@ -18,6 +18,19 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// Runtime selects the sandbox runtime family for a WorkerPool. It drives the +// worker pod shape (KVM/vhost devices, node placement); the concrete ateom +// binary is still chosen by AteomImage. +type Runtime string + +const ( + // RuntimeGvisor is the gVisor/runsc runtime (cmd/ateom-gvisor). Default. + RuntimeGvisor Runtime = "gvisor" + // RuntimeMicroVM is the kata + cloud-hypervisor micro-VM runtime + // (cmd/ateom-cloud-hypervisor); needs /dev/kvm and vhost devices. + RuntimeMicroVM Runtime = "microvm" +) + type WorkerPoolSpec struct { // Replicas is the number of worker pods to run. // +required @@ -28,6 +41,14 @@ type WorkerPoolSpec struct { // +kubebuilder:validation:MinLength=1 // +required AteomImage string `json:"ateomImage"` + + // Runtime selects the sandbox runtime family, which drives the worker pod + // shape (KVM/vhost device mounts and node placement). The concrete binary is + // still selected by AteomImage. Defaults to gvisor. + // +optional + // +kubebuilder:validation:Enum=gvisor;microvm + // +kubebuilder:default=gvisor + Runtime Runtime `json:"runtime,omitempty"` } type WorkerPoolStatus struct { diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 4bbfe02b6..383fca4f5 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -95,6 +95,7 @@ func (in *ActorTemplateSpec) DeepCopyInto(out *ActorTemplateSpec) { out.SnapshotsConfig = in.SnapshotsConfig out.WorkerPoolRef = in.WorkerPoolRef in.Runsc.DeepCopyInto(&out.Runsc) + in.Runtime.DeepCopyInto(&out.Runtime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActorTemplateSpec. @@ -278,6 +279,44 @@ func (in *RunscPlatformConfig) DeepCopy() *RunscPlatformConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuntimeAsset) DeepCopyInto(out *RuntimeAsset) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeAsset. +func (in *RuntimeAsset) DeepCopy() *RuntimeAsset { + if in == nil { + return nil + } + out := new(RuntimeAsset) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuntimeConfig) DeepCopyInto(out *RuntimeConfig) { + *out = *in + if in.Assets != nil { + in, out := &in.Assets, &out.Assets + *out = make(map[string]RuntimeAsset, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.Authentication.DeepCopyInto(&out.Authentication) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeConfig. +func (in *RuntimeConfig) DeepCopy() *RuntimeConfig { + if in == nil { + return nil + } + out := new(RuntimeConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { *out = *in From 808a9a138ecdf066fe6516cbdb94b4b33bcd315c Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:08 -0700 Subject: [PATCH 04/14] controller: micro-VM worker pod shape runtime=microvm worker pods get the /dev/kvm host device and are pinned to nested-virt nodes via nodeSelector + toleration on ate.dev/runtime=microvm. --- internal/controllers/workerpool_controller.go | 92 +++++++++++------ .../controllers/workerpool_podshape_test.go | 99 +++++++++++++++++++ 2 files changed, 162 insertions(+), 29 deletions(-) create mode 100644 internal/controllers/workerpool_podshape_test.go diff --git a/internal/controllers/workerpool_controller.go b/internal/controllers/workerpool_controller.go index e72d2befd..7e5a19dd2 100644 --- a/internal/controllers/workerpool_controller.go +++ b/internal/controllers/workerpool_controller.go @@ -120,6 +120,68 @@ func (r *WorkerPoolReconciler) syncStatus(ctx context.Context, wp *atev1alpha1.W // Deployment managed by a WorkerPool. Only fields owned by this controller // are declared here. func buildDeploymentApplyConfig(wp *atev1alpha1.WorkerPool) *appsv1ac.DeploymentApplyConfiguration { + container := corev1ac.Container(). + WithName("ateom"). + WithImage(wp.Spec.AteomImage). + WithArgs( + "--pod-uid=$(POD_UID)", + ). + WithSecurityContext(corev1ac.SecurityContext(). + WithPrivileged(true). + WithRunAsUser(0). + WithRunAsGroup(0)). + WithEnv( + corev1ac.EnvVar(). + WithName("POD_UID"). + WithValueFrom(corev1ac.EnvVarSource(). + WithFieldRef(corev1ac.ObjectFieldSelector(). + WithFieldPath("metadata.uid"))), + ). + WithVolumeMounts(corev1ac.VolumeMount(). + WithName("run-ateom"). + WithMountPath(ateompath.BasePath)) + + microVM := wp.Spec.Runtime == atev1alpha1.RuntimeMicroVM + + // The micro-VM runtime (kata + cloud-hypervisor) needs /dev/kvm. The + // container is already privileged (so it can also reach vhost devices), but + // we mount /dev/kvm explicitly. Mutate the container before it is added to + // the pod spec (WithContainers copies it by value). + if microVM { + container.WithVolumeMounts(corev1ac.VolumeMount(). + WithName("dev-kvm"). + WithMountPath("/dev/kvm")) + } + + podSpec := corev1ac.PodSpec(). + WithContainers(container). + WithSecurityContext(corev1ac.PodSecurityContext(). + WithRunAsUser(0). + WithRunAsGroup(0)). + WithVolumes(corev1ac.Volume(). + WithName("run-ateom"). + WithHostPath(corev1ac.HostPathVolumeSource(). + WithPath(ateompath.BasePath). + WithType(corev1.HostPathDirectoryOrCreate))) + + // Micro-VM pods must land on KVM-capable, nested-virt nodes: add the + // /dev/kvm host device and pin placement via nodeSelector + toleration on + // ate.dev/runtime=microvm. + if microVM { + podSpec. + WithVolumes(corev1ac.Volume(). + WithName("dev-kvm"). + WithHostPath(corev1ac.HostPathVolumeSource(). + WithPath("/dev/kvm"). + WithType(corev1.HostPathCharDev))). + WithNodeSelector(map[string]string{"ate.dev/runtime": string(atev1alpha1.RuntimeMicroVM)}). + WithTolerations(corev1ac.Toleration(). + WithKey("ate.dev/runtime"). + WithOperator(corev1.TolerationOpEqual). + WithValue(string(atev1alpha1.RuntimeMicroVM)). + WithEffect(corev1.TaintEffectNoSchedule)) + } + return appsv1ac.Deployment(deploymentName(wp.Name), wp.Namespace). WithOwnerReferences(metav1ac.OwnerReference(). WithAPIVersion(atev1alpha1.GroupVersion.String()). @@ -136,35 +198,7 @@ func buildDeploymentApplyConfig(wp *atev1alpha1.WorkerPool) *appsv1ac.Deployment WithLabels(map[string]string{ "ate.dev/worker-pool": wp.Name, }). - WithSpec(corev1ac.PodSpec(). - WithContainers(corev1ac.Container(). - WithName("ateom"). - WithImage(wp.Spec.AteomImage). - WithArgs( - "--pod-uid=$(POD_UID)", - ). - WithSecurityContext(corev1ac.SecurityContext(). - WithPrivileged(true). - WithRunAsUser(0). - WithRunAsGroup(0)). - WithEnv( - corev1ac.EnvVar(). - WithName("POD_UID"). - WithValueFrom(corev1ac.EnvVarSource(). - WithFieldRef(corev1ac.ObjectFieldSelector(). - WithFieldPath("metadata.uid"))), - ). - WithVolumeMounts(corev1ac.VolumeMount(). - WithName("run-ateom"). - WithMountPath(ateompath.BasePath))). - WithSecurityContext(corev1ac.PodSecurityContext(). - WithRunAsUser(0). - WithRunAsGroup(0)). - WithVolumes(corev1ac.Volume(). - WithName("run-ateom"). - WithHostPath(corev1ac.HostPathVolumeSource(). - WithPath(ateompath.BasePath). - WithType(corev1.HostPathDirectoryOrCreate)))))) + WithSpec(podSpec))) } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controllers/workerpool_podshape_test.go b/internal/controllers/workerpool_podshape_test.go new file mode 100644 index 000000000..5019218cf --- /dev/null +++ b/internal/controllers/workerpool_podshape_test.go @@ -0,0 +1,99 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" +) + +// TestBuildDeploymentPodShape asserts the micro-VM runtime adds the +// /dev/kvm device, nodeSelector, and toleration; gvisor (default) does not. +func TestBuildDeploymentPodShape(t *testing.T) { + tests := []struct { + name string + runtime atev1alpha1.Runtime + wantKVM bool + wantPlaced bool + }{ + {name: "gvisor default", runtime: "", wantKVM: false, wantPlaced: false}, + {name: "gvisor explicit", runtime: atev1alpha1.RuntimeGvisor, wantKVM: false, wantPlaced: false}, + {name: "microvm", runtime: atev1alpha1.RuntimeMicroVM, wantKVM: true, wantPlaced: true}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + wp := &atev1alpha1.WorkerPool{} + wp.Name = "wp" + wp.Namespace = "ns" + wp.Spec.Replicas = 1 + wp.Spec.AteomImage = "example.com/ateom:latest" + wp.Spec.Runtime = tc.runtime + + dep := buildDeploymentApplyConfig(wp) + ps := dep.Spec.Template.Spec + + // /dev/kvm volume + mount. + hasKVMVol := false + for _, v := range ps.Volumes { + if v.Name != nil && *v.Name == "dev-kvm" { + hasKVMVol = true + if v.HostPath == nil || v.HostPath.Path == nil || *v.HostPath.Path != "/dev/kvm" { + t.Errorf("dev-kvm volume hostPath = %+v, want /dev/kvm", v.HostPath) + } + if v.HostPath.Type == nil || *v.HostPath.Type != corev1.HostPathCharDev { + t.Errorf("dev-kvm volume type = %v, want CharDevice", v.HostPath.Type) + } + } + } + hasKVMMount := false + for _, c := range ps.Containers { + for _, m := range c.VolumeMounts { + if m.MountPath != nil && *m.MountPath == "/dev/kvm" { + hasKVMMount = true + } + } + } + if hasKVMVol != tc.wantKVM || hasKVMMount != tc.wantKVM { + t.Errorf("kvm present vol=%v mount=%v, want %v", hasKVMVol, hasKVMMount, tc.wantKVM) + } + + // nodeSelector + toleration on ate.dev/runtime. + _, hasSelector := ps.NodeSelector["ate.dev/runtime"] + hasToleration := false + for _, tol := range ps.Tolerations { + if tol.Key != nil && *tol.Key == "ate.dev/runtime" { + hasToleration = true + } + } + if hasSelector != tc.wantPlaced || hasToleration != tc.wantPlaced { + t.Errorf("placement selector=%v toleration=%v, want %v", hasSelector, hasToleration, tc.wantPlaced) + } + + // The base run-ateom hostPath mount must always be present. + foundBase := false + for _, v := range ps.Volumes { + if v.Name != nil && *v.Name == "run-ateom" { + foundBase = true + } + } + if !foundBase { + t.Error("run-ateom volume missing") + } + }) + } +} From ab03c26c44a3b0ef54f432ce37198fb288ee05da Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:08 -0700 Subject: [PATCH 05/14] atelet: fetch runtime assets + manifest-driven snapshots - Generalize the runsc fetch into content-addressed fetchRuntimeAssets (keyed asset map from the ActorTemplate, cached under the static-files dir), with per-template authentication selecting the storage client. - Checkpoint uploads exactly the files ateom reported (plus manifest.json); Restore downloads the manifest first, then the listed files. gVisor's fixed three-file set is preserved via the same path. --- cmd/atelet/main.go | 270 ++++++++++++++++++++++----- cmd/atelet/snapshot_manifest_test.go | 115 ++++++++++++ internal/ateompath/ateompath.go | 6 + 3 files changed, 340 insertions(+), 51 deletions(-) create mode 100644 cmd/atelet/snapshot_manifest_test.go diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index c4425bade..cf7b3aaa9 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -19,6 +19,7 @@ import ( "context" "crypto/sha256" "encoding/hex" + "encoding/json" "errors" "fmt" "log/slog" @@ -283,6 +284,81 @@ func (s *AteomHerder) fetchRunsc(ctx context.Context, cfg *ateletpb.RunscConfig) return localPath, nil } +// fetchRuntimeForRequest resolves the runtime assets for a request. For a +// non-gVisor runtime (RuntimeAssets set) it fetches the asset set and returns +// (name->localPath) with an empty runscPath; otherwise it fetches runsc and +// returns its path. Exactly one of the two is populated. +func (s *AteomHerder) fetchRuntimeForRequest(ctx context.Context, runsc *ateletpb.RunscConfig, rt *ateletpb.RuntimeAssetsConfig) (runscPath string, assetPaths map[string]string, err error) { + if rt != nil && rt.GetRuntime() != "" && rt.GetRuntime() != "gvisor" { + paths, err := s.fetchRuntimeAssets(ctx, rt) + return "", paths, err + } + p, err := s.fetchRunscAndPrep(ctx, runsc) + return p, nil, err +} + +// fetchRuntimeAssets fetches every asset in cfg content-addressed (like runsc), +// caching by sha256 under the static-files dir, and returns name->local path for +// atelet to pass to ateom. +func (s *AteomHerder) fetchRuntimeAssets(ctx context.Context, cfg *ateletpb.RuntimeAssetsConfig) (map[string]string, error) { + if err := os.MkdirAll(ateompath.StaticFilesDir, 0o700); err != nil { + return nil, fmt.Errorf("while creating static files dir: %w", err) + } + client := s.anonGCSClient + if cfg.GetAuthentication().GetGcp().GetUse() { + client = s.gcsClient + } + paths := make(map[string]string, len(cfg.GetAssets())) + for name, asset := range cfg.GetAssets() { + localPath := ateompath.RuntimeAssetPath(name, asset.GetSha256Hash()) + if _, err := os.Stat(localPath); err == nil { + paths[name] = localPath + continue + } else if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("while stat-ing %q: %w", localPath, err) + } + content, err := ategcs.FetchFromGCS(ctx, client, asset.GetUrl()) + if err != nil { + return nil, fmt.Errorf("while fetching asset %q from %v: %w", name, asset.GetUrl(), err) + } + sum := sha256.Sum256(content) + wantSum, err := hex.DecodeString(asset.GetSha256Hash()) + if err != nil { + return nil, fmt.Errorf("while parsing sha256 for %q: %w", name, err) + } + if !bytes.Equal(sum[:], wantSum) { + return nil, fmt.Errorf("sha256 mismatch for asset %q; got=%s want=%s", name, hex.EncodeToString(sum[:]), asset.GetSha256Hash()) + } + if err := atomicWriteFile(localPath, content, 0o755); err != nil { + return nil, fmt.Errorf("while writing asset %q: %w", name, err) + } + paths[name] = localPath + } + return paths, nil +} + +// atomicWriteFile writes content to a temp file in the same dir then renames it +// into place with the given mode. +func atomicWriteFile(path string, content []byte, mode os.FileMode) error { + tmp, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)+"-download-") + if err != nil { + return err + } + defer os.Remove(tmp.Name()) + if _, err := tmp.Write(content); err != nil { + tmp.Close() + return err + } + if err := tmp.Chmod(mode); err != nil { + tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + return os.Rename(tmp.Name(), path) +} + func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*ateletpb.RunResponse, error) { if err := validateRunRequest(req); err != nil { // status.Error so the interceptor surfaces InvalidArgument and the @@ -290,7 +366,7 @@ func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*atele return nil, status.Error(codes.InvalidArgument, err.Error()) } - runscPath, err := s.fetchRunscAndPrep(ctx, req.GetRunsc()) + runscPath, runtimeAssetPaths, err := s.fetchRuntimeForRequest(ctx, req.GetRunsc(), req.GetRuntimeAssets()) if err != nil { return nil, err } @@ -318,6 +394,7 @@ func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*atele ActorTemplateName: req.GetActorTemplateName(), ActorId: req.GetActorId(), RunscPath: runscPath, + RuntimeAssetPaths: runtimeAssetPaths, Spec: buildAteomWorkloadSpec(req.GetSpec()), }); err != nil { return nil, fmt.Errorf("while calling ateom.RunWorkload: %w", err) @@ -367,7 +444,7 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe return nil, status.Error(codes.InvalidArgument, err.Error()) } - runscPath, err := s.fetchRunscAndPrep(ctx, req.GetRunsc()) + runscPath, runtimeAssetPaths, err := s.fetchRuntimeForRequest(ctx, req.GetRunsc(), req.GetRuntimeAssets()) if err != nil { return nil, err } @@ -380,46 +457,55 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe } // Tell ateom to take checkpoint and delete containers. - if _, err := client.CheckpointWorkload(ctx, &ateompb.CheckpointWorkloadRequest{ + resp, err := client.CheckpointWorkload(ctx, &ateompb.CheckpointWorkloadRequest{ ActorTemplateNamespace: req.GetActorTemplateNamespace(), ActorTemplateName: req.GetActorTemplateName(), ActorId: req.GetActorId(), RunscPath: runscPath, + RuntimeAssetPaths: runtimeAssetPaths, Spec: buildAteomWorkloadSpec(req.GetSpec()), - }); err != nil { + }) + if err != nil { return nil, fmt.Errorf("while calling ateom.CheckpointWorkload: %w", err) } prefix := strings.TrimSuffix(req.GetSnapshotUriPrefix(), "/") ns, tmpl, actorID := req.GetActorTemplateNamespace(), req.GetActorTemplateName(), req.GetActorId() - checkpointImgPath := filepath.Join(checkpointDir, "checkpoint.img") - pagesImgPath := filepath.Join(checkpointDir, "pages.img") - pagesMetaImgPath := filepath.Join(checkpointDir, "pages_meta.img") - - recordSnapshotSize(ctx, "checkpoint", checkpointImgPath, ns, tmpl) - - // Upload checkpoint from local dir. - if err := ategcs.SendLocalFileToGCSWithZstd(ctx, s.gcsClient, - prefix+"/checkpoint.img.zstd", - checkpointImgPath, - ); err != nil { - return nil, fmt.Errorf("while uploading checkpoint.img to GCS: %w", err) - } - - recordSnapshotSize(ctx, "pages", pagesImgPath, ns, tmpl) - if err := uploadIfExists(ctx, s.gcsClient, - prefix+"/pages.img.zstd", - pagesImgPath, - ); err != nil { - return nil, err - } - recordSnapshotSize(ctx, "pages_meta", pagesMetaImgPath, ns, tmpl) - if err := uploadIfExists(ctx, s.gcsClient, - prefix+"/pages_meta.img.zstd", - pagesMetaImgPath, - ); err != nil { - return nil, err + if files := resp.GetSnapshotFiles(); len(files) > 0 { + // Runtime reported its own snapshot file set (e.g. cloud-hypervisor): + // ship exactly those files + a manifest. + recordSnapshotSize(ctx, "memory-ranges", filepath.Join(checkpointDir, "memory-ranges"), ns, tmpl) + if err := uploadSnapshotFiles(ctx, s.gcsClient, prefix, checkpointDir, files); err != nil { + return nil, err + } + } else { + // Legacy fixed file set (gVisor). + checkpointImgPath := filepath.Join(checkpointDir, "checkpoint.img") + pagesImgPath := filepath.Join(checkpointDir, "pages.img") + pagesMetaImgPath := filepath.Join(checkpointDir, "pages_meta.img") + + recordSnapshotSize(ctx, "checkpoint", checkpointImgPath, ns, tmpl) + if err := ategcs.SendLocalFileToGCSWithZstd(ctx, s.gcsClient, + prefix+"/checkpoint.img.zstd", + checkpointImgPath, + ); err != nil { + return nil, fmt.Errorf("while uploading checkpoint.img to GCS: %w", err) + } + recordSnapshotSize(ctx, "pages", pagesImgPath, ns, tmpl) + if err := uploadIfExists(ctx, s.gcsClient, + prefix+"/pages.img.zstd", + pagesImgPath, + ); err != nil { + return nil, err + } + recordSnapshotSize(ctx, "pages_meta", pagesMetaImgPath, ns, tmpl) + if err := uploadIfExists(ctx, s.gcsClient, + prefix+"/pages_meta.img.zstd", + pagesMetaImgPath, + ); err != nil { + return nil, err + } } if err := resetActorDirs(ns, tmpl, actorID); err != nil { @@ -434,7 +520,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) return nil, status.Error(codes.InvalidArgument, err.Error()) } - runscPath, err := s.fetchRunscAndPrep(ctx, req.GetRunsc()) + runscPath, runtimeAssetPaths, err := s.fetchRuntimeForRequest(ctx, req.GetRunsc(), req.GetRuntimeAssets()) if err != nil { return nil, err } @@ -446,27 +532,34 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) } checkpointDir := ateompath.RestoreStateDir(req.GetActorTemplateNamespace(), req.GetActorTemplateName(), req.GetActorId()) - checkpointImgPath := filepath.Join(checkpointDir, "checkpoint.img") - pagesImgPath := filepath.Join(checkpointDir, "pages.img") - pagesMetaImgPath := filepath.Join(checkpointDir, "pages_meta.img") - prefix := strings.TrimSuffix(req.GetSnapshotUriPrefix(), "/") - g, gCtx := errgroup.WithContext(ctx) - for _, dl := range []struct{ remote, local string }{ - {prefix + "/checkpoint.img.zstd", checkpointImgPath}, - {prefix + "/pages.img.zstd", pagesImgPath}, - {prefix + "/pages_meta.img.zstd", pagesMetaImgPath}, - } { - dl := dl - g.Go(func() error { - if err := ategcs.FetchLocalFileFromGCSWithZstd(gCtx, s.gcsClient, dl.remote, dl.local); err != nil { - return fmt.Errorf("while downloading %s from GCS: %w", filepath.Base(dl.remote), err) - } - return nil - }) - } - if err := g.Wait(); err != nil { + + // Prefer a manifest-based snapshot (e.g. cloud-hypervisor); fall back to the + // legacy fixed file set (gVisor) when there is no manifest. + if ok, err := downloadSnapshotManifest(ctx, s.gcsClient, prefix, checkpointDir); err != nil { return nil, err + } else if !ok { + checkpointImgPath := filepath.Join(checkpointDir, "checkpoint.img") + pagesImgPath := filepath.Join(checkpointDir, "pages.img") + pagesMetaImgPath := filepath.Join(checkpointDir, "pages_meta.img") + + g, gCtx := errgroup.WithContext(ctx) + for _, dl := range []struct{ remote, local string }{ + {prefix + "/checkpoint.img.zstd", checkpointImgPath}, + {prefix + "/pages.img.zstd", pagesImgPath}, + {prefix + "/pages_meta.img.zstd", pagesMetaImgPath}, + } { + dl := dl + g.Go(func() error { + if err := ategcs.FetchLocalFileFromGCSWithZstd(gCtx, s.gcsClient, dl.remote, dl.local); err != nil { + return fmt.Errorf("while downloading %s from GCS: %w", filepath.Base(dl.remote), err) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return nil, err + } } if err := s.prepareOCIBundles(ctx, ns, tmpl, actorID, @@ -487,6 +580,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) ActorTemplateName: tmpl, ActorId: actorID, RunscPath: runscPath, + RuntimeAssetPaths: runtimeAssetPaths, Spec: buildAteomWorkloadSpec(req.GetSpec()), }); err != nil { return nil, fmt.Errorf("while calling ateom.RestoreWorkload: %w", err) @@ -607,6 +701,80 @@ func uploadIfExists(ctx context.Context, gcs ategcs.ObjectStorage, remoteURI, lo return nil } +// snapshotManifestName is the object (under the snapshot prefix) that lists the +// files comprising a runtime-reported snapshot. Its presence distinguishes a +// manifest-based snapshot (e.g. cloud-hypervisor) from the legacy gVisor fixed +// file set. +const snapshotManifestName = "manifest.json" + +type snapshotManifest struct { + Files []string `json:"files"` +} + +// uploadSnapshotFiles ships a runtime-reported snapshot file set (each +// zstd-compressed) plus a manifest written last so its presence signals a +// complete upload. Used for runtimes that populate +// CheckpointWorkloadResponse.snapshot_files (e.g. cloud-hypervisor). +func uploadSnapshotFiles(ctx context.Context, gcs ategcs.ObjectStorage, prefix, dir string, files []string) error { + g, gCtx := errgroup.WithContext(ctx) + for _, f := range files { + f := f + g.Go(func() error { + if err := ategcs.SendLocalFileToGCSWithZstd(gCtx, gcs, prefix+"/"+f+".zstd", filepath.Join(dir, f)); err != nil { + return fmt.Errorf("while uploading %s: %w", f, err) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return err + } + manifestPath := filepath.Join(dir, snapshotManifestName) + b, err := json.Marshal(snapshotManifest{Files: files}) + if err != nil { + return err + } + if err := os.WriteFile(manifestPath, b, 0o600); err != nil { + return err + } + if err := ategcs.SendLocalFileToGCSWithZstd(ctx, gcs, prefix+"/"+snapshotManifestName+".zstd", manifestPath); err != nil { + return fmt.Errorf("while uploading snapshot manifest: %w", err) + } + return nil +} + +// downloadSnapshotManifest fetches the manifest (if any) and every listed file +// into dir. Returns ok=false when no manifest exists, so the caller falls back +// to the legacy fixed file set (gVisor). +func downloadSnapshotManifest(ctx context.Context, gcs ategcs.ObjectStorage, prefix, dir string) (bool, error) { + manifestPath := filepath.Join(dir, snapshotManifestName) + if err := ategcs.FetchLocalFileFromGCSWithZstd(ctx, gcs, prefix+"/"+snapshotManifestName+".zstd", manifestPath); err != nil { + return false, nil // no manifest -> legacy snapshot + } + b, err := os.ReadFile(manifestPath) + if err != nil { + return false, err + } + var m snapshotManifest + if err := json.Unmarshal(b, &m); err != nil { + return false, fmt.Errorf("parsing snapshot manifest: %w", err) + } + g, gCtx := errgroup.WithContext(ctx) + for _, f := range m.Files { + f := f + g.Go(func() error { + if err := ategcs.FetchLocalFileFromGCSWithZstd(gCtx, gcs, prefix+"/"+f+".zstd", filepath.Join(dir, f)); err != nil { + return fmt.Errorf("while downloading %s: %w", f, err) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return false, err + } + return true, nil +} + type AteomDialer struct { conns *lru.Cache } diff --git a/cmd/atelet/snapshot_manifest_test.go b/cmd/atelet/snapshot_manifest_test.go new file mode 100644 index 000000000..6905f9bee --- /dev/null +++ b/cmd/atelet/snapshot_manifest_test.go @@ -0,0 +1,115 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path/filepath" + "sync" + "testing" +) + +// memStore is an in-memory ategcs.ObjectStorage for testing. +type memStore struct { + mu sync.Mutex + data map[string][]byte +} + +func (m *memStore) key(bucket, object string) string { return bucket + "/" + object } + +func (m *memStore) GetObject(ctx context.Context, bucket, object string) (io.ReadCloser, error) { + m.mu.Lock() + defer m.mu.Unlock() + b, ok := m.data[m.key(bucket, object)] + if !ok { + return nil, fmt.Errorf("object not found: %s/%s", bucket, object) + } + return io.NopCloser(bytes.NewReader(b)), nil +} + +func (m *memStore) PutObject(ctx context.Context, bucket, object string, r io.Reader) error { + b, err := io.ReadAll(r) + if err != nil { + return err + } + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = map[string][]byte{} + } + m.data[m.key(bucket, object)] = b + return nil +} + +// TestSnapshotManifestRoundTrip verifies the cloud-hypervisor snapshot ship path: +// uploadSnapshotFiles writes a manifest + each file, and downloadSnapshotManifest +// reconstructs them byte-for-byte at the destination. +func TestSnapshotManifestRoundTrip(t *testing.T) { + store := &memStore{data: map[string][]byte{}} + ctx := context.Background() + + src := t.TempDir() + files := []string{"config.json", "state.json", "memory-ranges", "shared-dir.tar"} + want := map[string][]byte{} + for i, f := range files { + content := []byte(fmt.Sprintf("contents-of-%s-%d\x00\x01\x02", f, i)) + if err := os.WriteFile(filepath.Join(src, f), content, 0o600); err != nil { + t.Fatal(err) + } + want[f] = content + } + + prefix := "gs://test-bucket/actors/x/snapshots/y" + if err := uploadSnapshotFiles(ctx, store, prefix, src, files); err != nil { + t.Fatalf("uploadSnapshotFiles: %v", err) + } + + dst := t.TempDir() + ok, err := downloadSnapshotManifest(ctx, store, prefix, dst) + if err != nil { + t.Fatalf("downloadSnapshotManifest: %v", err) + } + if !ok { + t.Fatal("downloadSnapshotManifest returned ok=false, want true") + } + for _, f := range files { + got, err := os.ReadFile(filepath.Join(dst, f)) + if err != nil { + t.Errorf("restored %q: %v", f, err) + continue + } + if !bytes.Equal(got, want[f]) { + t.Errorf("restored %q = %q, want %q", f, got, want[f]) + } + } +} + +// TestDownloadSnapshotManifestMissing verifies the legacy fallback: with no +// manifest in storage, downloadSnapshotManifest reports ok=false (no error) so +// the caller uses the fixed gVisor file set. +func TestDownloadSnapshotManifestMissing(t *testing.T) { + store := &memStore{data: map[string][]byte{}} + ok, err := downloadSnapshotManifest(context.Background(), store, "gs://test-bucket/none", t.TempDir()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if ok { + t.Error("ok = true for missing manifest, want false") + } +} diff --git a/internal/ateompath/ateompath.go b/internal/ateompath/ateompath.go index 304d53f5f..60142a065 100644 --- a/internal/ateompath/ateompath.go +++ b/internal/ateompath/ateompath.go @@ -34,6 +34,12 @@ func RunSCBinaryPath(sha256 string) string { return filepath.Join(StaticFilesDir, "runsc-"+sha256) } +// RuntimeAssetPath is the content-addressed local path for a fetched non-gVisor +// runtime asset (named so multiple asset kinds don't collide). +func RuntimeAssetPath(name, sha256 string) string { + return filepath.Join(StaticFilesDir, "asset-"+name+"-"+sha256) +} + func AteomPath(podUID string) string { return filepath.Join( BasePath, From 3e3977bf5a8eb120f0ccdbd2f184c35c0db70210 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:09 -0700 Subject: [PATCH 06/14] ateapi: thread runtime assets into suspend/resume workflows --- .../internal/controlapi/workflow_resume.go | 25 +++++++++++++++++++ .../internal/controlapi/workflow_suspend.go | 1 + 2 files changed, 26 insertions(+) diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index 9f06eec0d..42298e472 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -33,6 +33,26 @@ import ( "k8s.io/client-go/kubernetes" ) +// buildRuntimeAssetsConfig maps an ActorTemplate RuntimeConfig to the ateletpb +// RuntimeAssetsConfig atelet uses to fetch a non-gVisor runtime's assets. +// Returns nil for gVisor (empty type / no assets), so the runsc path is used. +func buildRuntimeAssetsConfig(rt atev1alpha1.RuntimeConfig) *ateletpb.RuntimeAssetsConfig { + if rt.Type == "" || rt.Type == string(atev1alpha1.RuntimeGvisor) || len(rt.Assets) == 0 { + return nil + } + cfg := &ateletpb.RuntimeAssetsConfig{ + Runtime: rt.Type, + Assets: make(map[string]*ateletpb.RuntimeAsset, len(rt.Assets)), + } + for name, a := range rt.Assets { + cfg.Assets[name] = &ateletpb.RuntimeAsset{Sha256Hash: a.SHA256Hash, Url: a.URL} + } + if rt.Authentication.GCP != nil { + cfg.Authentication = &ateletpb.AuthenticationConfig{Gcp: &ateletpb.GCPAuthenticationConfig{Use: true}} + } + return cfg +} + // ResumeInput holds the immutable parameters requested by the client. type ResumeInput struct { ActorID string @@ -199,6 +219,8 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, runscCfg.Authentication = authnCfg } + runtimeCfg := buildRuntimeAssetsConfig(state.ActorTemplate.Spec.Runtime) + if state.Actor.LastSnapshot != "" { slog.InfoContext(ctx, "Actor has snapshot; Restoring from snapshot") @@ -208,6 +230,7 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, ActorTemplateName: state.Actor.GetActorTemplateName(), ActorId: state.Actor.GetActorId(), Runsc: runscCfg, + RuntimeAssets: runtimeCfg, Spec: workloadSpec, SnapshotUriPrefix: state.Actor.GetLastSnapshot(), } @@ -227,6 +250,7 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, ActorTemplateName: state.Actor.GetActorTemplateName(), ActorId: state.Actor.GetActorId(), Runsc: runscCfg, + RuntimeAssets: runtimeCfg, Spec: workloadSpec, SnapshotUriPrefix: snapshot, } @@ -243,6 +267,7 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, ActorTemplateName: state.Actor.GetActorTemplateName(), ActorId: state.Actor.GetActorId(), Runsc: runscCfg, + RuntimeAssets: runtimeCfg, Spec: workloadSpec, } _, err = client.Run(ctx, req) diff --git a/cmd/ateapi/internal/controlapi/workflow_suspend.go b/cmd/ateapi/internal/controlapi/workflow_suspend.go index d8210574b..ee7ac614f 100644 --- a/cmd/ateapi/internal/controlapi/workflow_suspend.go +++ b/cmd/ateapi/internal/controlapi/workflow_suspend.go @@ -141,6 +141,7 @@ func (s *CallAteletSuspendStep) Execute(ctx context.Context, input *SuspendInput ActorTemplateName: state.Actor.GetActorTemplateName(), ActorId: state.Actor.GetActorId(), Runsc: runscCfg, + RuntimeAssets: buildRuntimeAssetsConfig(state.ActorTemplate.Spec.Runtime), Spec: &ateletpb.WorkloadSpec{ PauseImage: state.ActorTemplate.Spec.PauseImage, }, From 8030c698ad9b13caa9e56d129e8366e459b058d9 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:53 -0700 Subject: [PATCH 07/14] ateom-cloud-hypervisor: kata + cloud-hypervisor micro-VM runtime A peer to cmd/ateom-gvisor implementing ateompb.Ateom with full suspend/resume: - Run: drive containerd-shim-kata-v2 directly over ttrpc (no containerd daemon), foreground shim server, rendered configuration.toml pointing at runtime-fetched assets (fetch-not-bake). /run/kata-containers is made rshared at boot so kata's virtio-fs mounts/->shared/ propagation works inside a pod (runc roots are rprivate). - Networking mirrors ateom-gvisor: pod eth0 moves into a per-pod interior netns; kata builds its tap + TC mirror there. Stale taps/qdiscs and leftover per-sandbox state/processes are cleaned before each run (the sandbox id is the actor id, so retries collide otherwise). - Checkpoint: pause (idempotent via vm.info), capture the virtio-fs shared dir (nsenter into the shim mountns, or locally for restored actors), CH native snapshot, teardown; reports the snapshot file list. - Restore: ateom-owned bare-CH relaunch. The snapshot's virtio-net is fd-backed, so restore recreates the tap + mirror and passes fresh tap FDs via vm.restore net_fds (SCM_RIGHTS over the api-socket). Snapshot socket paths are rewritten to the restoring actor's VMDir; a patched virtiofsd (find-paths migration mode) serves the reconstructed shared dir. - Guest IP mobility: the snapshot freezes the source pod's IP, so restore re-addresses guest eth0 to the new pod's IP via the kata-agent UpdateInterface/UpdateRoutes ttrpc RPCs over hybrid vsock (agentpb is a minimal wire-compatible mirror of the kata 3.31 protos). The address is /32 + gateway routes so forwarding works on CNIs without host-veth proxy-ARP (GKE) as well as those with it (kind ptp). Long-lived processes (shim, CH, virtiofsd) are deliberately started without exec.CommandContext: gRPC cancels the request ctx when the handler returns, which would SIGKILL them under a healthy actor. Verified end-to-end on kind (arm64, cross-pod) and GKE (amd64 nested-virt node pool, cross-node across per-node pod subnets): an in-RAM counter continues counting across suspend -> object storage -> restore. --- .ko.yaml | 5 + cmd/ateom-cloud-hypervisor/internal/ch/api.go | 130 +++ cmd/ateom-cloud-hypervisor/internal/ch/ch.go | 189 +++++ .../internal/ch/ch_test.go | 179 ++++ .../internal/ch/restorefds.go | 181 ++++ .../internal/kata/agentclient.go | 102 +++ .../internal/kata/agentpb/agent.pb.go | 588 +++++++++++++ .../internal/kata/agentpb/agent.proto | 77 ++ .../internal/kata/agentpb/gen.go | 17 + .../kata/checkpoint_integration_test.go | 179 ++++ .../internal/kata/cleanup_linux.go | 91 ++ .../internal/kata/config.go | 114 +++ .../internal/kata/config_test.go | 91 ++ .../internal/kata/kata.go | 357 ++++++++ .../internal/kata/kata_integration_test.go | 283 +++++++ .../internal/kata/restore.go | 211 +++++ .../internal/kata/restore_integration_test.go | 186 ++++ cmd/ateom-cloud-hypervisor/main.go | 270 ++++++ .../main_unsupported.go | 27 + cmd/ateom-cloud-hypervisor/net.go | 365 ++++++++ cmd/ateom-cloud-hypervisor/run.go | 793 ++++++++++++++++++ .../service_integration_test.go | 216 +++++ 22 files changed, 4651 insertions(+) create mode 100644 cmd/ateom-cloud-hypervisor/internal/ch/api.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/ch/ch.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/ch/ch_test.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/ch/restorefds.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/checkpoint_integration_test.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/cleanup_linux.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/config.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/config_test.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/kata.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/kata_integration_test.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/restore.go create mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/restore_integration_test.go create mode 100644 cmd/ateom-cloud-hypervisor/main.go create mode 100644 cmd/ateom-cloud-hypervisor/main_unsupported.go create mode 100644 cmd/ateom-cloud-hypervisor/net.go create mode 100644 cmd/ateom-cloud-hypervisor/run.go create mode 100644 cmd/ateom-cloud-hypervisor/service_integration_test.go diff --git a/.ko.yaml b/.ko.yaml index 774665cec..e95fcd58e 100644 --- a/.ko.yaml +++ b/.ko.yaml @@ -21,3 +21,8 @@ defaultPlatforms: baseImageOverrides: github.com/agent-substrate/substrate/demos/sandbox: alpine github.com/agent-substrate/substrate/demos/agent-secret: alpine + # ateom-cloud-hypervisor shells out to nsenter + tar (to capture the kata + # shim's virtio-fs shared dir) and execs the fetched cloud-hypervisor/virtiofsd + # (Rust, glibc-linked) binaries. debian-slim provides util-linux (nsenter), + # tar, and glibc — NOT distroless/static. Kata/CH assets are fetched at runtime. + github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor: debian:stable-slim diff --git a/cmd/ateom-cloud-hypervisor/internal/ch/api.go b/cmd/ateom-cloud-hypervisor/internal/ch/api.go new file mode 100644 index 000000000..97bf6832a --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/ch/api.go @@ -0,0 +1,130 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ch + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" +) + +// apiBase is a placeholder host; the real transport always dials the unix +// api-socket, so the host portion of the URL is ignored. +const apiBase = "http://localhost" + +// apiClient speaks the cloud-hypervisor REST API over its unix api-socket. +// +// cloud-hypervisor serves an HTTP/1.1 REST API on the api-socket, and we drive snapshot/restore +// through it (vm.pause, vm.snapshot, vm.resume, vmm.ping, ...). +type apiClient struct { + http *http.Client +} + +func newAPIClient(socketPath string) *apiClient { + return &apiClient{ + http: &http.Client{ + Transport: &http.Transport{ + // CH's API server closes idle connections (and can get heavily + // swapped out during reclaim). Reusing a kept-alive connection + // then blocks forever on the next request (observed + // empirically: vm.resume hangs on a reused connection while + // a fresh one works instantly). Force a fresh connection per + // request. + DisableKeepAlives: true, + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, "unix", socketPath) + }, + }, + }, + } +} + +// get issues a GET and checks for a 2xx status. +func (c *apiClient) get(ctx context.Context, path string) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiBase+path, nil) + if err != nil { + return err + } + resp, err := c.http.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + _, _ = io.Copy(io.Discard, resp.Body) + if resp.StatusCode >= 300 { + return fmt.Errorf("GET %s: status %d", path, resp.StatusCode) + } + return nil +} + +// getJSON issues a GET and decodes the 2xx JSON response into out. +func (c *apiClient) getJSON(ctx context.Context, path string, out any) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiBase+path, nil) + if err != nil { + return err + } + resp, err := c.http.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + if resp.StatusCode >= 300 { + return fmt.Errorf("GET %s: status %d: %s", path, resp.StatusCode, bytes.TrimSpace(b)) + } + return json.Unmarshal(b, out) +} + +// put issues a PUT with an optional JSON body and checks for a 2xx status. +func (c *apiClient) put(ctx context.Context, path string, body any) error { + var rdr io.Reader + if body != nil { + b, err := json.Marshal(body) + if err != nil { + return err + } + rdr = bytes.NewReader(b) + } + req, err := http.NewRequestWithContext(ctx, http.MethodPut, apiBase+path, rdr) + if err != nil { + return err + } + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + resp, err := c.http.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + msg, _ := io.ReadAll(resp.Body) + if resp.StatusCode >= 300 { + return fmt.Errorf("PUT %s: status %d: %s", path, resp.StatusCode, bytes.TrimSpace(msg)) + } + return nil +} + +// snapshotConfig is the body of /api/v1/vm.snapshot. +type snapshotConfig struct { + DestinationURL string `json:"destination_url"` +} diff --git a/cmd/ateom-cloud-hypervisor/internal/ch/ch.go b/cmd/ateom-cloud-hypervisor/internal/ch/ch.go new file mode 100644 index 000000000..29ba903af --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/ch/ch.go @@ -0,0 +1,189 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package ch drives a single cloud-hypervisor instance over its REST +// api-socket: pause, snapshot, resume against a running VMM (e.g. the socket +// kata creates at /run/vc/vm//clh-api.sock), plus relaunching a fresh VMM +// from a snapshot directory for restore. +// +// This is the snapshot/restore half of the ateom-cloud-hypervisor model: kata +// owns RUN (boot the micro-VM + run the OCI container), and ateom drives the CH +// REST API underneath for suspend (pause+snapshot) and owns the bare-CH +// relaunch for restore. The REST wire format and the --restore CLI form are +// the ones cloud-hypervisor documents for snapshot/restore. +package ch + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "time" +) + +// Client talks to one cloud-hypervisor VMM over its unix api-socket. +type Client struct { + apiSocket string + api *apiClient +} + +// NewClient returns a Client bound to a cloud-hypervisor api-socket path. The +// socket need not exist yet; use WaitReady to block until the VMM answers. +func NewClient(apiSocket string) *Client { + return &Client{apiSocket: apiSocket, api: newAPIClient(apiSocket)} +} + +// APISocket returns the api-socket path this client is bound to. +func (c *Client) APISocket() string { return c.apiSocket } + +// Ping returns nil if the VMM api-socket answers vmm.ping. +func (c *Client) Ping(ctx context.Context) error { + return c.api.get(ctx, "/api/v1/vmm.ping") +} + +// WaitReady blocks until the api-socket answers vmm.ping or the deadline passes. +func (c *Client) WaitReady(ctx context.Context, deadline time.Duration) error { + end := time.Now().Add(deadline) + for { + if err := c.Ping(ctx); err == nil { + return nil + } + if !time.Now().Before(end) { + return fmt.Errorf("cloud-hypervisor api socket %q not ready after %s", c.apiSocket, deadline) + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(10 * time.Millisecond): + } + } +} + +// State returns the VM state as reported by vm.info (e.g. "Running", "Paused"). +func (c *Client) State(ctx context.Context) (string, error) { + var info struct { + State string `json:"state"` + } + if err := c.api.getJSON(ctx, "/api/v1/vm.info", &info); err != nil { + return "", err + } + return info.State, nil +} + +// Pause pauses the running guest (quiescing it before snapshot). Idempotent: +// already-paused is success (CH itself 500s on pausing a paused VM, which would +// otherwise wedge checkpoint retries after a partial earlier attempt). +func (c *Client) Pause(ctx context.Context) error { + if state, err := c.State(ctx); err == nil && state == "Paused" { + return nil + } + return c.api.put(ctx, "/api/v1/vm.pause", nil) +} + +// Resume resumes a paused guest (after snapshot or restore). +func (c *Client) Resume(ctx context.Context) error { + return c.api.put(ctx, "/api/v1/vm.resume", nil) +} + +// Snapshot writes the (paused) guest's state to destDir as a CH snapshot +// (config.json + state.json + memory-ranges). The guest must be paused first. +func (c *Client) Snapshot(ctx context.Context, destDir string) error { + if err := os.MkdirAll(destDir, 0o755); err != nil { + return fmt.Errorf("while creating snapshot dir %q: %w", destDir, err) + } + return c.api.put(ctx, "/api/v1/vm.snapshot", snapshotConfig{DestinationURL: SnapshotURL(destDir)}) +} + +// Shutdown best-effort tears down the VM and the VMM process behind the socket. +func (c *Client) Shutdown(ctx context.Context) error { + _ = c.api.put(ctx, "/api/v1/vm.shutdown", nil) + return c.api.put(ctx, "/api/v1/vmm.shutdown", nil) +} + +// SnapshotURL returns the file:// URL cloud-hypervisor expects for a snapshot +// destination or restore source directory. +func SnapshotURL(dir string) string { return "file://" + dir } + +// RestoreOptions configures relaunching a fresh cloud-hypervisor process from a +// snapshot directory (the restore half of suspend/resume). +type RestoreOptions struct { + // Binary is the cloud-hypervisor executable (defaults to "cloud-hypervisor" + // on PATH if empty). + Binary string + // APISocket is the api-socket path the new VMM should listen on. + APISocket string + // SourceDir is the snapshot directory to restore from. + SourceDir string + // MemoryRestoreMode selects how guest RAM is brought back: "" or "copy" for + // eager copy (CH default), "ondemand" for userfaultfd demand-paging. + MemoryRestoreMode string + // ExtraArgs are appended verbatim (e.g. cgroup/log flags). + ExtraArgs []string + // Stdout/Stderr capture the VMM process output (e.g. a vmm.log file). + Stdout io.Writer + Stderr io.Writer +} + +// RestoreArgs builds the cloud-hypervisor argv for restoring from a snapshot. +// Pure (no I/O) so it can be unit tested. +func RestoreArgs(o RestoreOptions) []string { + restoreArg := "source_url=" + SnapshotURL(o.SourceDir) + switch o.MemoryRestoreMode { + case "ondemand": + // ondemand uses userfaultfd; prefault must be off. + restoreArg += ",memory_restore_mode=ondemand,prefault=off" + case "", "copy": + // Eager copy is CH's default; leave memory_restore_mode unset. + } + args := []string{"--api-socket", o.APISocket, "--restore", restoreArg} + return append(args, o.ExtraArgs...) +} + +// Restore launches a fresh cloud-hypervisor process with --restore and waits +// until its api-socket answers. The restored VM comes back paused; call Resume +// on the returned Client to run it. The caller owns cmd (must Wait/Kill it). +func Restore(ctx context.Context, o RestoreOptions) (cmd *exec.Cmd, client *Client, err error) { + if o.APISocket == "" { + return nil, nil, fmt.Errorf("RestoreOptions.APISocket is required") + } + if o.SourceDir == "" { + return nil, nil, fmt.Errorf("RestoreOptions.SourceDir is required") + } + bin := o.Binary + if bin == "" { + bin = "cloud-hypervisor" + } + // A stale socket from a prior VMM blocks bind; remove it best-effort. + _ = os.Remove(o.APISocket) + + // Deliberately NOT exec.CommandContext: the restored VMM must outlive the + // Restore RPC whose ctx launched it (gRPC cancels the ctx when the handler + // returns, which would SIGKILL the freshly restored VM). The caller owns + // cmd; the WaitReady below honors ctx for bootstrap cancellation. + cmd = exec.Command(bin, RestoreArgs(o)...) + cmd.Stdout = o.Stdout + cmd.Stderr = o.Stderr + if err := cmd.Start(); err != nil { + return nil, nil, fmt.Errorf("while starting cloud-hypervisor --restore: %w", err) + } + + client = NewClient(o.APISocket) + if err := client.WaitReady(ctx, 15*time.Second); err != nil { + _ = cmd.Process.Kill() + _, _ = cmd.Process.Wait() + return nil, nil, fmt.Errorf("while waiting for restored VMM: %w", err) + } + return cmd, client, nil +} diff --git a/cmd/ateom-cloud-hypervisor/internal/ch/ch_test.go b/cmd/ateom-cloud-hypervisor/internal/ch/ch_test.go new file mode 100644 index 000000000..4e2da1d38 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/ch/ch_test.go @@ -0,0 +1,179 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ch + +import ( + "context" + "encoding/json" + "io" + "net" + "net/http" + "path/filepath" + "reflect" + "sync" + "testing" + "time" +) + +func TestSnapshotURL(t *testing.T) { + if got, want := SnapshotURL("/var/lib/snap"), "file:///var/lib/snap"; got != want { + t.Errorf("SnapshotURL = %q, want %q", got, want) + } +} + +func TestRestoreArgs(t *testing.T) { + tests := []struct { + name string + opts RestoreOptions + want []string + }{ + { + name: "default copy mode", + opts: RestoreOptions{APISocket: "/run/ch.sock", SourceDir: "/snap"}, + want: []string{"--api-socket", "/run/ch.sock", "--restore", "source_url=file:///snap"}, + }, + { + name: "explicit copy mode", + opts: RestoreOptions{APISocket: "/run/ch.sock", SourceDir: "/snap", MemoryRestoreMode: "copy"}, + want: []string{"--api-socket", "/run/ch.sock", "--restore", "source_url=file:///snap"}, + }, + { + name: "ondemand mode", + opts: RestoreOptions{APISocket: "/run/ch.sock", SourceDir: "/snap", MemoryRestoreMode: "ondemand"}, + want: []string{"--api-socket", "/run/ch.sock", "--restore", "source_url=file:///snap,memory_restore_mode=ondemand,prefault=off"}, + }, + { + name: "extra args appended", + opts: RestoreOptions{APISocket: "/run/ch.sock", SourceDir: "/snap", ExtraArgs: []string{"--log-file", "/tmp/vmm.log"}}, + want: []string{"--api-socket", "/run/ch.sock", "--restore", "source_url=file:///snap", "--log-file", "/tmp/vmm.log"}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := RestoreArgs(tc.opts); !reflect.DeepEqual(got, tc.want) { + t.Errorf("RestoreArgs() = %v, want %v", got, tc.want) + } + }) + } +} + +// fakeCH is a stand-in cloud-hypervisor REST server on a unix socket. It records +// the requests it receives so tests can assert on method/path/body. +type fakeCH struct { + mu sync.Mutex + requests []recordedReq + srv *http.Server +} + +type recordedReq struct { + method string + path string + body string +} + +func startFakeCH(t *testing.T) (*Client, *fakeCH) { + t.Helper() + sockPath := filepath.Join(t.TempDir(), "ch.sock") + lis, err := net.Listen("unix", sockPath) + if err != nil { + t.Fatalf("listen unix: %v", err) + } + + f := &fakeCH{} + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + f.mu.Lock() + f.requests = append(f.requests, recordedReq{method: r.Method, path: r.URL.Path, body: string(body)}) + f.mu.Unlock() + w.WriteHeader(http.StatusNoContent) + }) + f.srv = &http.Server{Handler: mux} + go f.srv.Serve(lis) + t.Cleanup(func() { _ = f.srv.Close() }) + + return NewClient(sockPath), f +} + +func (f *fakeCH) recorded() []recordedReq { + f.mu.Lock() + defer f.mu.Unlock() + out := make([]recordedReq, len(f.requests)) + copy(out, f.requests) + return out +} + +func TestClientLifecycleCalls(t *testing.T) { + client, fake := startFakeCH(t) + ctx := context.Background() + + if err := client.WaitReady(ctx, time.Second); err != nil { + t.Fatalf("WaitReady: %v", err) + } + if err := client.Pause(ctx); err != nil { + t.Fatalf("Pause: %v", err) + } + snapDir := filepath.Join(t.TempDir(), "snap") + if err := client.Snapshot(ctx, snapDir); err != nil { + t.Fatalf("Snapshot: %v", err) + } + if err := client.Resume(ctx); err != nil { + t.Fatalf("Resume: %v", err) + } + + reqs := fake.recorded() + // First request is the WaitReady ping; assert the meaningful ones after it. + var got []recordedReq + for _, r := range reqs { + if r.path == "/api/v1/vmm.ping" { + continue + } + got = append(got, r) + } + want := []recordedReq{ + // Pause checks vm.info first (idempotency); the fake's empty reply is an + // unparseable state, so Pause falls through to the actual vm.pause PUT. + {method: http.MethodGet, path: "/api/v1/vm.info", body: ""}, + {method: http.MethodPut, path: "/api/v1/vm.pause", body: ""}, + {method: http.MethodPut, path: "/api/v1/vm.snapshot"}, + {method: http.MethodPut, path: "/api/v1/vm.resume", body: ""}, + } + if len(got) != len(want) { + t.Fatalf("got %d non-ping requests %+v, want %d", len(got), got, len(want)) + } + for i := range want { + if got[i].method != want[i].method || got[i].path != want[i].path { + t.Errorf("request %d = %s %s, want %s %s", i, got[i].method, got[i].path, want[i].method, want[i].path) + } + } + + // The snapshot body must carry the file:// destination URL. + var snap snapshotConfig + if err := json.Unmarshal([]byte(got[2].body), &snap); err != nil { + t.Fatalf("snapshot body not JSON: %v (%q)", err, got[2].body) + } + if snap.DestinationURL != SnapshotURL(snapDir) { + t.Errorf("snapshot destination_url = %q, want %q", snap.DestinationURL, SnapshotURL(snapDir)) + } +} + +func TestWaitReadyTimesOut(t *testing.T) { + // Socket that never exists -> WaitReady should time out, not hang. + client := NewClient(filepath.Join(t.TempDir(), "nonexistent.sock")) + err := client.WaitReady(context.Background(), 50*time.Millisecond) + if err == nil { + t.Fatal("WaitReady returned nil for a dead socket, want timeout error") + } +} diff --git a/cmd/ateom-cloud-hypervisor/internal/ch/restorefds.go b/cmd/ateom-cloud-hypervisor/internal/ch/restorefds.go new file mode 100644 index 000000000..4f4767cf2 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/ch/restorefds.go @@ -0,0 +1,181 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ch + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "net" + "os" + "os/exec" + "strings" + "time" + + "golang.org/x/sys/unix" +) + +// RestoredNet identifies one fd-backed network device in a snapshot and the +// fresh tap FDs to back it with on restore. kata boots CH virtio-net devices +// from tap FDs, so the snapshot's config requires net_fds on restore +// (RestoreMissingRequiredNetId otherwise); CH reopens the device on the FDs we +// pass over the api-socket via SCM_RIGHTS. +type RestoredNet struct { + // ID is the device id from the snapshot's config.json (e.g. "_net1"). + ID string + // FDs are open tap fds (one per queue pair) for CH to adopt. + FDs []int +} + +// LaunchVMMOptions configures starting a bare VMM (no VM) for an FD-passing +// restore. +type LaunchVMMOptions struct { + // Binary is the cloud-hypervisor executable (defaults to "cloud-hypervisor"). + Binary string + // APISocket is the api-socket path the new VMM should listen on. + APISocket string + // Stdout/Stderr receive the VMM's output. + Stdout, Stderr interface{ Write([]byte) (int, error) } +} + +// LaunchVMM starts a cloud-hypervisor process with only an api-socket (no VM) +// and waits until it answers. Use Client.RestoreWithNetFDs to then restore a +// snapshot that has fd-backed net devices. The caller owns cmd. +func LaunchVMM(ctx context.Context, o LaunchVMMOptions) (*exec.Cmd, *Client, error) { + if o.APISocket == "" { + return nil, nil, fmt.Errorf("LaunchVMMOptions.APISocket is required") + } + bin := o.Binary + if bin == "" { + bin = "cloud-hypervisor" + } + _ = os.Remove(o.APISocket) + // Deliberately NOT exec.CommandContext: the VMM must outlive the RPC whose + // ctx launched it. The caller owns cmd; WaitReady honors ctx. + cmd := exec.Command(bin, "--api-socket", o.APISocket) + cmd.Stdout = o.Stdout + cmd.Stderr = o.Stderr + if err := cmd.Start(); err != nil { + return nil, nil, fmt.Errorf("while starting cloud-hypervisor: %w", err) + } + client := NewClient(o.APISocket) + if err := client.WaitReady(ctx, 15*time.Second); err != nil { + _ = cmd.Process.Kill() + _, _ = cmd.Process.Wait() + return nil, nil, fmt.Errorf("while waiting for VMM api-socket: %w", err) + } + return cmd, client, nil +} + +// RestoreWithNetFDs issues vm.restore for a snapshot dir, passing fresh tap FDs +// for the snapshot's fd-backed net devices via SCM_RIGHTS on the api-socket +// (the only way CH accepts net FDs on restore; mirrors ch-remote's +// send_with_fds). The VM comes back paused; call Resume after. +func (c *Client) RestoreWithNetFDs(ctx context.Context, sourceDir string, nets []RestoredNet) error { + type restoredNetConfig struct { + ID string `json:"id"` + NumFDs int `json:"num_fds"` + } + cfg := struct { + SourceURL string `json:"source_url"` + NetFDs []restoredNetConfig `json:"net_fds,omitempty"` + }{SourceURL: SnapshotURL(sourceDir)} + var fds []int + for _, n := range nets { + cfg.NetFDs = append(cfg.NetFDs, restoredNetConfig{ID: n.ID, NumFDs: len(n.FDs)}) + fds = append(fds, n.FDs...) + } + body, err := json.Marshal(cfg) + if err != nil { + return err + } + + raddr, err := net.ResolveUnixAddr("unix", c.apiSocket) + if err != nil { + return err + } + conn, err := net.DialUnix("unix", nil, raddr) + if err != nil { + return fmt.Errorf("dialing api-socket: %w", err) + } + defer conn.Close() + if dl, ok := ctx.Deadline(); ok { + _ = conn.SetDeadline(dl) + } else { + _ = conn.SetDeadline(time.Now().Add(60 * time.Second)) + } + + // Raw HTTP/1.1 over the unix socket: net/http cannot attach SCM_RIGHTS, and + // CH's micro_http collects fds from the recvmsg ancillary data of the + // request that carries them. + req := fmt.Sprintf("PUT /api/v1/vm.restore HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s", len(body), body) + var oob []byte + if len(fds) > 0 { + oob = unix.UnixRights(fds...) + } + if _, _, err := conn.WriteMsgUnix([]byte(req), oob, nil); err != nil { + return fmt.Errorf("sending vm.restore with fds: %w", err) + } + + status, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + return fmt.Errorf("reading vm.restore response: %w", err) + } + parts := strings.SplitN(strings.TrimSpace(status), " ", 3) + if len(parts) < 2 || !strings.HasPrefix(parts[1], "2") { + return fmt.Errorf("vm.restore failed: %s", strings.TrimSpace(status)) + } + return nil +} + +// SnapshotNetDevice describes one net device found in a CH snapshot's +// config.json. Restore must supply net_fds for every one of them. +type SnapshotNetDevice struct { + // ID is the CH device id (e.g. "_net1"). + ID string + // QueuePairs is the number of tap FDs the device needs (num_queues/2). + QueuePairs int + // MAC is the guest-visible MAC address of the device. + MAC string +} + +// SnapshotNetDevices parses a CH snapshot's config.json and returns its net +// devices, in order. +func SnapshotNetDevices(snapshotDir string) ([]SnapshotNetDevice, error) { + b, err := os.ReadFile(snapshotDir + "/config.json") + if err != nil { + return nil, err + } + var cfg struct { + Net []struct { + ID string `json:"id"` + NumQueues int `json:"num_queues"` + MAC string `json:"mac"` + } `json:"net"` + } + if err := json.Unmarshal(b, &cfg); err != nil { + return nil, fmt.Errorf("parsing snapshot config.json: %w", err) + } + var out []SnapshotNetDevice + for _, n := range cfg.Net { + qp := n.NumQueues / 2 + if qp < 1 { + qp = 1 + } + out = append(out, SnapshotNetDevice{ID: n.ID, QueuePairs: qp, MAC: n.MAC}) + } + return out, nil +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go b/cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go new file mode 100644 index 000000000..4f1f495c7 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go @@ -0,0 +1,102 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "bufio" + "context" + "fmt" + "net" + "strings" + "time" + + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpb" + "github.com/containerd/ttrpc" +) + +// agentVsockPort is the guest port the kata-agent's ttrpc server listens on. +const agentVsockPort = 1024 + +// AgentClient is a thin ttrpc client for the kata-agent RPCs ateom drives +// directly (network reconfiguration after a CH snapshot restore). It dials the +// agent through CH's hybrid-vsock unix socket — the same channel the kata shim +// uses — so it works on a restored VM with no shim present. +type AgentClient struct { + conn net.Conn + client *ttrpc.Client +} + +// DialAgent connects to the kata-agent through the hybrid-vsock socket at +// vsockPath (VsockSocketPath(id)): plain-text "CONNECT " handshake with +// the VMM, then ttrpc over the stream. +func DialAgent(ctx context.Context, vsockPath string) (*AgentClient, error) { + d := net.Dialer{} + conn, err := d.DialContext(ctx, "unix", vsockPath) + if err != nil { + return nil, fmt.Errorf("dialing hybrid vsock %q: %w", vsockPath, err) + } + if dl, ok := ctx.Deadline(); ok { + _ = conn.SetDeadline(dl) + } else { + _ = conn.SetDeadline(time.Now().Add(10 * time.Second)) + } + if _, err := fmt.Fprintf(conn, "CONNECT %d\n", agentVsockPort); err != nil { + conn.Close() + return nil, fmt.Errorf("hybrid vsock CONNECT: %w", err) + } + line, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + conn.Close() + return nil, fmt.Errorf("hybrid vsock CONNECT response: %w", err) + } + if !strings.HasPrefix(line, "OK ") { + conn.Close() + return nil, fmt.Errorf("hybrid vsock CONNECT refused: %q", strings.TrimSpace(line)) + } + _ = conn.SetDeadline(time.Time{}) // ttrpc manages its own timeouts via ctx + return &AgentClient{conn: conn, client: ttrpc.NewClient(conn)}, nil +} + +// Close shuts the ttrpc client and underlying connection. +func (a *AgentClient) Close() error { + err := a.client.Close() + _ = a.conn.Close() + return err +} + +// UpdateInterface replaces the addresses (and mtu/name binding) of the guest +// interface named by iface.Name/Device with the provided spec, returning the +// resulting interface state. Mirrors grpc.AgentService/UpdateInterface. +func (a *AgentClient) UpdateInterface(ctx context.Context, iface *agentpb.Interface) (*agentpb.Interface, error) { + resp := &agentpb.Interface{} + if err := a.client.Call(ctx, "grpc.AgentService", "UpdateInterface", + &agentpb.UpdateInterfaceRequest{Interface: iface}, resp); err != nil { + return nil, fmt.Errorf("agent UpdateInterface: %w", err) + } + return resp, nil +} + +// UpdateRoutes replaces the guest routing table with the provided routes, +// returning the resulting routes. Mirrors grpc.AgentService/UpdateRoutes. +func (a *AgentClient) UpdateRoutes(ctx context.Context, routes []*agentpb.Route) ([]*agentpb.Route, error) { + resp := &agentpb.Routes{} + if err := a.client.Call(ctx, "grpc.AgentService", "UpdateRoutes", + &agentpb.UpdateRoutesRequest{Routes: &agentpb.Routes{Routes: routes}}, resp); err != nil { + return nil, fmt.Errorf("agent UpdateRoutes: %w", err) + } + return resp.GetRoutes(), nil +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go new file mode 100644 index 000000000..73ab9afe8 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go @@ -0,0 +1,588 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Minimal wire-compatible mirror of the kata-agent ttrpc API surface +// ateom-cloud-hypervisor needs after a CH snapshot restore: updating the +// restored guest's network interface and routes to the new pod's addresses. +// +// Field numbers and types mirror kata-containers 3.31.0 +// src/libs/protocols/protos/{agent,types}.proto (Apache-2.0). The service is +// NOT declared here; calls go through ttrpc.Client.Call with the literal +// method names ("grpc.AgentService", "UpdateInterface"/"UpdateRoutes") so no +// ttrpc codegen plugin is required. kata's messages live in proto packages +// "grpc" and "types"; only field numbers matter on the wire, so they are +// flattened into this single package (type registration names differ, which +// is fine — names are never transmitted). + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11-devel +// protoc v4.25.3 +// source: agent.proto + +package agentpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type IPFamily int32 + +const ( + IPFamily_v4 IPFamily = 0 + IPFamily_v6 IPFamily = 1 +) + +// Enum value maps for IPFamily. +var ( + IPFamily_name = map[int32]string{ + 0: "v4", + 1: "v6", + } + IPFamily_value = map[string]int32{ + "v4": 0, + "v6": 1, + } +) + +func (x IPFamily) Enum() *IPFamily { + p := new(IPFamily) + *p = x + return p +} + +func (x IPFamily) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (IPFamily) Descriptor() protoreflect.EnumDescriptor { + return file_agent_proto_enumTypes[0].Descriptor() +} + +func (IPFamily) Type() protoreflect.EnumType { + return &file_agent_proto_enumTypes[0] +} + +func (x IPFamily) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use IPFamily.Descriptor instead. +func (IPFamily) EnumDescriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{0} +} + +type IPAddress struct { + state protoimpl.MessageState `protogen:"open.v1"` + Family IPFamily `protobuf:"varint,1,opt,name=family,proto3,enum=ateomkataagent.IPFamily" json:"family,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Mask string `protobuf:"bytes,3,opt,name=mask,proto3" json:"mask,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IPAddress) Reset() { + *x = IPAddress{} + mi := &file_agent_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IPAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IPAddress) ProtoMessage() {} + +func (x *IPAddress) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IPAddress.ProtoReflect.Descriptor instead. +func (*IPAddress) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{0} +} + +func (x *IPAddress) GetFamily() IPFamily { + if x != nil { + return x.Family + } + return IPFamily_v4 +} + +func (x *IPAddress) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *IPAddress) GetMask() string { + if x != nil { + return x.Mask + } + return "" +} + +type Interface struct { + state protoimpl.MessageState `protogen:"open.v1"` + Device string `protobuf:"bytes,1,opt,name=device,proto3" json:"device,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + IPAddresses []*IPAddress `protobuf:"bytes,3,rep,name=IPAddresses,proto3" json:"IPAddresses,omitempty"` + Mtu uint64 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"` + HwAddr string `protobuf:"bytes,5,opt,name=hwAddr,proto3" json:"hwAddr,omitempty"` + DevicePath string `protobuf:"bytes,6,opt,name=devicePath,proto3" json:"devicePath,omitempty"` + Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` + RawFlags uint32 `protobuf:"varint,8,opt,name=raw_flags,json=rawFlags,proto3" json:"raw_flags,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Interface) Reset() { + *x = Interface{} + mi := &file_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Interface) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Interface) ProtoMessage() {} + +func (x *Interface) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Interface.ProtoReflect.Descriptor instead. +func (*Interface) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{1} +} + +func (x *Interface) GetDevice() string { + if x != nil { + return x.Device + } + return "" +} + +func (x *Interface) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Interface) GetIPAddresses() []*IPAddress { + if x != nil { + return x.IPAddresses + } + return nil +} + +func (x *Interface) GetMtu() uint64 { + if x != nil { + return x.Mtu + } + return 0 +} + +func (x *Interface) GetHwAddr() string { + if x != nil { + return x.HwAddr + } + return "" +} + +func (x *Interface) GetDevicePath() string { + if x != nil { + return x.DevicePath + } + return "" +} + +func (x *Interface) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Interface) GetRawFlags() uint32 { + if x != nil { + return x.RawFlags + } + return 0 +} + +type Route struct { + state protoimpl.MessageState `protogen:"open.v1"` + Dest string `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"` + Gateway string `protobuf:"bytes,2,opt,name=gateway,proto3" json:"gateway,omitempty"` + Device string `protobuf:"bytes,3,opt,name=device,proto3" json:"device,omitempty"` + Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` + Scope uint32 `protobuf:"varint,5,opt,name=scope,proto3" json:"scope,omitempty"` + Family IPFamily `protobuf:"varint,6,opt,name=family,proto3,enum=ateomkataagent.IPFamily" json:"family,omitempty"` + Flags uint32 `protobuf:"varint,7,opt,name=flags,proto3" json:"flags,omitempty"` + Mtu uint32 `protobuf:"varint,8,opt,name=mtu,proto3" json:"mtu,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Route) Reset() { + *x = Route{} + mi := &file_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Route) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Route) ProtoMessage() {} + +func (x *Route) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Route.ProtoReflect.Descriptor instead. +func (*Route) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{2} +} + +func (x *Route) GetDest() string { + if x != nil { + return x.Dest + } + return "" +} + +func (x *Route) GetGateway() string { + if x != nil { + return x.Gateway + } + return "" +} + +func (x *Route) GetDevice() string { + if x != nil { + return x.Device + } + return "" +} + +func (x *Route) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *Route) GetScope() uint32 { + if x != nil { + return x.Scope + } + return 0 +} + +func (x *Route) GetFamily() IPFamily { + if x != nil { + return x.Family + } + return IPFamily_v4 +} + +func (x *Route) GetFlags() uint32 { + if x != nil { + return x.Flags + } + return 0 +} + +func (x *Route) GetMtu() uint32 { + if x != nil { + return x.Mtu + } + return 0 +} + +type Routes struct { + state protoimpl.MessageState `protogen:"open.v1"` + Routes []*Route `protobuf:"bytes,1,rep,name=Routes,proto3" json:"Routes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Routes) Reset() { + *x = Routes{} + mi := &file_agent_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Routes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Routes) ProtoMessage() {} + +func (x *Routes) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Routes.ProtoReflect.Descriptor instead. +func (*Routes) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{3} +} + +func (x *Routes) GetRoutes() []*Route { + if x != nil { + return x.Routes + } + return nil +} + +type UpdateInterfaceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interface *Interface `protobuf:"bytes,1,opt,name=interface,proto3" json:"interface,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateInterfaceRequest) Reset() { + *x = UpdateInterfaceRequest{} + mi := &file_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateInterfaceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateInterfaceRequest) ProtoMessage() {} + +func (x *UpdateInterfaceRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateInterfaceRequest.ProtoReflect.Descriptor instead. +func (*UpdateInterfaceRequest) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{4} +} + +func (x *UpdateInterfaceRequest) GetInterface() *Interface { + if x != nil { + return x.Interface + } + return nil +} + +type UpdateRoutesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Routes *Routes `protobuf:"bytes,1,opt,name=routes,proto3" json:"routes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateRoutesRequest) Reset() { + *x = UpdateRoutesRequest{} + mi := &file_agent_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateRoutesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRoutesRequest) ProtoMessage() {} + +func (x *UpdateRoutesRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRoutesRequest.ProtoReflect.Descriptor instead. +func (*UpdateRoutesRequest) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{5} +} + +func (x *UpdateRoutesRequest) GetRoutes() *Routes { + if x != nil { + return x.Routes + } + return nil +} + +var File_agent_proto protoreflect.FileDescriptor + +const file_agent_proto_rawDesc = "" + + "\n" + + "\vagent.proto\x12\x0eateomkataagent\"k\n" + + "\tIPAddress\x120\n" + + "\x06family\x18\x01 \x01(\x0e2\x18.ateomkataagent.IPFamilyR\x06family\x12\x18\n" + + "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x12\n" + + "\x04mask\x18\x03 \x01(\tR\x04mask\"\xef\x01\n" + + "\tInterface\x12\x16\n" + + "\x06device\x18\x01 \x01(\tR\x06device\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12;\n" + + "\vIPAddresses\x18\x03 \x03(\v2\x19.ateomkataagent.IPAddressR\vIPAddresses\x12\x10\n" + + "\x03mtu\x18\x04 \x01(\x04R\x03mtu\x12\x16\n" + + "\x06hwAddr\x18\x05 \x01(\tR\x06hwAddr\x12\x1e\n" + + "\n" + + "devicePath\x18\x06 \x01(\tR\n" + + "devicePath\x12\x12\n" + + "\x04type\x18\a \x01(\tR\x04type\x12\x1b\n" + + "\traw_flags\x18\b \x01(\rR\brawFlags\"\xd5\x01\n" + + "\x05Route\x12\x12\n" + + "\x04dest\x18\x01 \x01(\tR\x04dest\x12\x18\n" + + "\agateway\x18\x02 \x01(\tR\agateway\x12\x16\n" + + "\x06device\x18\x03 \x01(\tR\x06device\x12\x16\n" + + "\x06source\x18\x04 \x01(\tR\x06source\x12\x14\n" + + "\x05scope\x18\x05 \x01(\rR\x05scope\x120\n" + + "\x06family\x18\x06 \x01(\x0e2\x18.ateomkataagent.IPFamilyR\x06family\x12\x14\n" + + "\x05flags\x18\a \x01(\rR\x05flags\x12\x10\n" + + "\x03mtu\x18\b \x01(\rR\x03mtu\"7\n" + + "\x06Routes\x12-\n" + + "\x06Routes\x18\x01 \x03(\v2\x15.ateomkataagent.RouteR\x06Routes\"Q\n" + + "\x16UpdateInterfaceRequest\x127\n" + + "\tinterface\x18\x01 \x01(\v2\x19.ateomkataagent.InterfaceR\tinterface\"E\n" + + "\x13UpdateRoutesRequest\x12.\n" + + "\x06routes\x18\x01 \x01(\v2\x16.ateomkataagent.RoutesR\x06routes*\x1a\n" + + "\bIPFamily\x12\x06\n" + + "\x02v4\x10\x00\x12\x06\n" + + "\x02v6\x10\x01BWZUgithub.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpbb\x06proto3" + +var ( + file_agent_proto_rawDescOnce sync.Once + file_agent_proto_rawDescData []byte +) + +func file_agent_proto_rawDescGZIP() []byte { + file_agent_proto_rawDescOnce.Do(func() { + file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc))) + }) + return file_agent_proto_rawDescData +} + +var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_agent_proto_goTypes = []any{ + (IPFamily)(0), // 0: ateomkataagent.IPFamily + (*IPAddress)(nil), // 1: ateomkataagent.IPAddress + (*Interface)(nil), // 2: ateomkataagent.Interface + (*Route)(nil), // 3: ateomkataagent.Route + (*Routes)(nil), // 4: ateomkataagent.Routes + (*UpdateInterfaceRequest)(nil), // 5: ateomkataagent.UpdateInterfaceRequest + (*UpdateRoutesRequest)(nil), // 6: ateomkataagent.UpdateRoutesRequest +} +var file_agent_proto_depIdxs = []int32{ + 0, // 0: ateomkataagent.IPAddress.family:type_name -> ateomkataagent.IPFamily + 1, // 1: ateomkataagent.Interface.IPAddresses:type_name -> ateomkataagent.IPAddress + 0, // 2: ateomkataagent.Route.family:type_name -> ateomkataagent.IPFamily + 3, // 3: ateomkataagent.Routes.Routes:type_name -> ateomkataagent.Route + 2, // 4: ateomkataagent.UpdateInterfaceRequest.interface:type_name -> ateomkataagent.Interface + 4, // 5: ateomkataagent.UpdateRoutesRequest.routes:type_name -> ateomkataagent.Routes + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_agent_proto_init() } +func file_agent_proto_init() { + if File_agent_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)), + NumEnums: 1, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_agent_proto_goTypes, + DependencyIndexes: file_agent_proto_depIdxs, + EnumInfos: file_agent_proto_enumTypes, + MessageInfos: file_agent_proto_msgTypes, + }.Build() + File_agent_proto = out.File + file_agent_proto_goTypes = nil + file_agent_proto_depIdxs = nil +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto new file mode 100644 index 000000000..029120fa3 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto @@ -0,0 +1,77 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Minimal wire-compatible mirror of the kata-agent ttrpc API surface +// ateom-cloud-hypervisor needs after a CH snapshot restore: updating the +// restored guest's network interface and routes to the new pod's addresses. +// +// Field numbers and types mirror kata-containers 3.31.0 +// src/libs/protocols/protos/{agent,types}.proto (Apache-2.0). The service is +// NOT declared here; calls go through ttrpc.Client.Call with the literal +// method names ("grpc.AgentService", "UpdateInterface"/"UpdateRoutes") so no +// ttrpc codegen plugin is required. kata's messages live in proto packages +// "grpc" and "types"; only field numbers matter on the wire, so they are +// flattened into this single package (type registration names differ, which +// is fine — names are never transmitted). + +syntax = "proto3"; + +package ateomkataagent; + +option go_package = "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpb"; + +enum IPFamily { + v4 = 0; + v6 = 1; +} + +message IPAddress { + IPFamily family = 1; + string address = 2; + string mask = 3; +} + +message Interface { + string device = 1; + string name = 2; + repeated IPAddress IPAddresses = 3; + uint64 mtu = 4; + string hwAddr = 5; + string devicePath = 6; + string type = 7; + uint32 raw_flags = 8; +} + +message Route { + string dest = 1; + string gateway = 2; + string device = 3; + string source = 4; + uint32 scope = 5; + IPFamily family = 6; + uint32 flags = 7; + uint32 mtu = 8; +} + +message Routes { + repeated Route Routes = 1; +} + +message UpdateInterfaceRequest { + Interface interface = 1; +} + +message UpdateRoutesRequest { + Routes routes = 1; +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go new file mode 100644 index 000000000..6165bfd62 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go @@ -0,0 +1,17 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package agentpb + +//go:generate bash -c "../../../../../hack/protoc.sh --plugin=protoc-gen-go=$(bash ../../../../../hack/run-tool.sh --print-bin-path protoc-gen-go) --go_out=paths=source_relative:. agent.proto" diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/checkpoint_integration_test.go b/cmd/ateom-cloud-hypervisor/internal/kata/checkpoint_integration_test.go new file mode 100644 index 000000000..8380bffb1 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/checkpoint_integration_test.go @@ -0,0 +1,179 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/ch" + "golang.org/x/sys/unix" +) + +// TestKataCheckpoint boots a busybox container in a CH micro-VM via the kata +// shim, then exercises the checkpoint path: drive CH's REST api-socket (the +// one kata created) to pause + snapshot, and verify a portable, sparse snapshot +// directory is produced. Gated behind KATA_INTEGRATION=1 (see kata_integration_test.go). +func TestKataCheckpoint(t *testing.T) { + if os.Getenv("KATA_INTEGRATION") != "1" { + t.Skip("set KATA_INTEGRATION=1 to run (requires kata + /dev/kvm)") + } + rootfsSrc := os.Getenv("KATA_ROOTFS_SRC") + if rootfsSrc == "" { + t.Fatal("KATA_ROOTFS_SRC is required") + } + shim := os.Getenv("KATA_SHIM") + if shim == "" { + shim = "/opt/kata/bin/containerd-shim-kata-v2" + } + + id := fmt.Sprintf("ateomchv-ckpt-%d", os.Getpid()) + work := filepath.Join("/tmp", id) + bundle := filepath.Join(work, "bundle") + rootfs := filepath.Join(bundle, "rootfs") + if err := os.MkdirAll(rootfs, 0o755); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.RemoveAll(work) }) + if out, err := exec.Command("cp", "-a", rootfsSrc+"/.", rootfs+"/").CombinedOutput(); err != nil { + t.Fatalf("copying rootfs: %v: %s", err, out) + } + writeBundleConfig(t, bundle, id) + drainLogFifo(t, bundle) + + s := &Shim{ + Binary: shim, + ID: id, + Bundle: bundle, + Namespace: "default", + GRPCAddress: filepath.Join(work, "fake-containerd.sock"), + TTRPCAddress: filepath.Join(work, "fake-containerd.sock.ttrpc"), + Diagnostics: testWriter{t}, + Debug: true, + } + + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + defer func() { + _ = s.Kill(ctx, 9, true) + sc, c := context.WithTimeout(context.Background(), 15*time.Second) + _ = s.Shutdown(sc, true) + c() + _ = s.Close() + cc, c2 := context.WithTimeout(context.Background(), 15*time.Second) + _ = s.CleanupAction(cc) + c2() + }() + + if err := s.Bootstrap(ctx); err != nil { + t.Fatalf("Bootstrap: %v", err) + } + if _, err := s.Create(ctx, CreateOptions{}); err != nil { + t.Fatalf("Create: %v", err) + } + if _, err := s.Start(ctx); err != nil { + t.Fatalf("Start: %v", err) + } + t.Log("micro-VM running; checkpointing via CH REST api-socket") + + // --- M2: drive the CH api-socket kata created. --- + client := ch.NewClient(CLHSocketPath(id)) + if err := client.WaitReady(ctx, 10*time.Second); err != nil { + t.Fatalf("CH WaitReady: %v", err) + } + if err := client.Pause(ctx); err != nil { + t.Fatalf("CH Pause: %v", err) + } + snapDir := filepath.Join(work, "snapshot") + if err := client.Snapshot(ctx, snapDir); err != nil { + t.Fatalf("CH Snapshot: %v", err) + } + t.Logf("snapshot written to %s", snapDir) + + // Verify the snapshot dir: config.json + state.json + a sparse memory file. + for _, f := range []string{"config.json", "state.json"} { + if _, err := os.Stat(filepath.Join(snapDir, f)); err != nil { + t.Errorf("expected snapshot file %q: %v", f, err) + } + } + entries, err := os.ReadDir(snapDir) + if err != nil { + t.Fatalf("reading snapshot dir: %v", err) + } + var memFile string + for _, e := range entries { + t.Logf("snapshot entry: %s", e.Name()) + if e.Name() != "config.json" && e.Name() != "state.json" { + memFile = e.Name() + } + } + if memFile == "" { + t.Fatal("no memory file in snapshot dir") + } + apparent, actual := fileSizes(t, filepath.Join(snapDir, memFile)) + t.Logf("memory file %q: apparent=%d bytes, actual(on-disk)=%d bytes", memFile, apparent, actual) + if actual >= apparent { + t.Errorf("memory file not sparse: actual %d >= apparent %d (need shared=on / sparse snapshot)", actual, apparent) + } + if apparent == 0 { + t.Errorf("memory file is empty") + } + t.Log("checkpoint produced a portable, sparse snapshot") +} + +// drainLogFifo creates the bundle "log" fifo the kata shim writes to and drains +// it to t.Log so the shim's diagnostics are visible (and it doesn't block). +func drainLogFifo(t *testing.T, bundle string) { + t.Helper() + logFifo := filepath.Join(bundle, "log") + if err := unix.Mkfifo(logFifo, 0o700); err != nil { + t.Fatalf("mkfifo log: %v", err) + } + go func() { + f, err := os.OpenFile(logFifo, os.O_RDONLY, 0) + if err != nil { + return + } + defer f.Close() + buf := make([]byte, 4096) + for { + n, err := f.Read(buf) + if n > 0 { + t.Logf("[shim-log] %s", buf[:n]) + } + if err != nil { + return + } + } + }() +} + +// fileSizes returns a file's apparent size and its actual on-disk size (from +// st_blocks), so a caller can detect sparseness. +func fileSizes(t *testing.T, path string) (apparent, actual int64) { + t.Helper() + var st unix.Stat_t + if err := unix.Stat(path, &st); err != nil { + t.Fatalf("stat %q: %v", path, err) + } + return st.Size, st.Blocks * 512 +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/cleanup_linux.go b/cmd/ateom-cloud-hypervisor/internal/kata/cleanup_linux.go new file mode 100644 index 000000000..10ed0a321 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/cleanup_linux.go @@ -0,0 +1,91 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "os" + "path/filepath" + "sort" + "strconv" + "strings" + + "golang.org/x/sys/unix" +) + +// CleanupSandboxState removes kata's leftover host-side state for a sandbox id +// (the shared sandbox dir, the per-VM runtime dir, and the sbs dir), lazily +// unmounting anything still mounted underneath them first, and kills orphaned +// per-sandbox processes. Because ateom drives the shim directly (no +// containerd), a failed Create does not fully self-clean; the deterministic +// sandbox id (= actor id) then collides on the next attempt: "listen unix +// .../virtiofsd.sock: bind: address already in use", "Could not bind mount +// .../shared/sandboxes//mounts", "directory not empty". Calling this +// before each run gives kata a clean slate. Safe when nothing exists. +func CleanupSandboxState(id string) { + dirs := []string{ + filepath.Join("/run/kata-containers/shared/sandboxes", id), + filepath.Join(vcVMDir, id), + filepath.Join("/run/vc/sbs", id), + } + if b, err := os.ReadFile("/proc/self/mountinfo"); err == nil { + var mounts []string + for _, line := range strings.Split(string(b), "\n") { + fields := strings.Fields(line) + if len(fields) < 5 { + continue + } + mp := fields[4] // mount point + for _, d := range dirs { + if mp == d || strings.HasPrefix(mp, d+"/") { + mounts = append(mounts, mp) + break + } + } + } + // Deepest paths first so child mounts unmount before their parents. + sort.Slice(mounts, func(i, j int) bool { return len(mounts[i]) > len(mounts[j]) }) + for _, mp := range mounts { + _ = unix.Unmount(mp, unix.MNT_DETACH) + } + } + for _, d := range dirs { + _ = os.RemoveAll(d) + } + // Kill orphaned per-sandbox processes (cloud-hypervisor / virtiofsd / shim) + // left by a prior killed attempt: a canceled Create leaves the CH it spawned + // running (reparented to us) holding guest RAM and stale sockets. Matched + // strictly by the sandbox id (an actor UUID) appearing in the cmdline, so + // nothing unrelated can match. + entries, err := os.ReadDir("/proc") + if err != nil { + return + } + for _, e := range entries { + pid, perr := strconv.Atoi(e.Name()) + if perr != nil || pid == os.Getpid() { + continue + } + cmdline, rerr := os.ReadFile(filepath.Join("/proc", e.Name(), "cmdline")) + if rerr != nil || !strings.Contains(string(cmdline), id) { + continue + } + argv0 := strings.SplitN(string(cmdline), "\x00", 2)[0] + if strings.Contains(argv0, "cloud-hypervisor") || strings.Contains(argv0, "virtiofsd") || strings.Contains(argv0, "containerd-shim-kata") { + _ = unix.Kill(pid, unix.SIGKILL) + } + } +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/config.go b/cmd/ateom-cloud-hypervisor/internal/kata/config.go new file mode 100644 index 000000000..d1ac84e49 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/config.go @@ -0,0 +1,114 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "fmt" + "regexp" +) + +// ConfigAssets are the runtime-fetched asset paths to splice into a kata +// configuration.toml so the worker image needs no baked /opt/kata. Each is an +// absolute on-node path (content-addressed under the static-files dir, like runsc). +type ConfigAssets struct { + // Kernel is the guest kernel (vmlinux) path. + Kernel string + // Image is the guest OS rootfs image path. + Image string + // Hypervisor is the cloud-hypervisor binary path. + Hypervisor string + // Virtiofsd is the virtiofsd binary path (needs find-paths migration + // support, >= 1.13; kata bundles an older build without it). + Virtiofsd string +} + +// configField is a single TOML key whose value we rewrite to a fetched path. +type configField struct { + key string + value func(ConfigAssets) string +} + +// pathFields are the asset-path keys in a clh configuration.toml. We rewrite +// each (and its valid_* allowlist) to the fetched path. Empirically validated: +// a stock config with only these lines rewritten boots a VM via KATA_CONF_FILE. +var pathFields = []configField{ + {"kernel", func(a ConfigAssets) string { return quote(a.Kernel) }}, + {"image", func(a ConfigAssets) string { return quote(a.Image) }}, + {"path", func(a ConfigAssets) string { return quote(a.Hypervisor) }}, + {"valid_hypervisor_paths", func(a ConfigAssets) string { return list(a.Hypervisor) }}, + {"virtio_fs_daemon", func(a ConfigAssets) string { return quote(a.Virtiofsd) }}, + {"valid_virtio_fs_daemon_paths", func(a ConfigAssets) string { return list(a.Virtiofsd) }}, +} + +func quote(s string) string { return `"` + s + `"` } +func list(s string) string { return `["` + s + `"]` } + +// EnableDebug turns on kata's debug knobs in a configuration.toml: it uncomments +// every `#enable_debug = true` (hypervisor/agent/runtime) and appends +// `agent.log=debug` to the hypervisor kernel_params so the guest kata-agent emits +// debug-level logs (with the failing path on errors) over its vsock log channel, +// which the shim relays to our log fifo -> pod logs. POC diagnostic aid. +func EnableDebug(base []byte) []byte { + out := base + // Uncomment all `#enable_debug = true` lines. + reDbg := regexp.MustCompile(`(?m)^(\s*)#\s*enable_debug\s*=\s*true\s*$`) + out = reDbg.ReplaceAll(out, []byte("${1}enable_debug = true")) + // Append agent.log=debug to kernel_params (only if not already present). + reKP := regexp.MustCompile(`(?m)^(\s*kernel_params\s*=\s*")([^"]*)(".*)$`) + out = reKP.ReplaceAllFunc(out, func(line []byte) []byte { + m := reKP.FindSubmatch(line) + if m == nil { + return line + } + existing := string(m[2]) + if regexpContains(existing, "agent.log=") { + return line + } + val := "agent.log=debug agent.debug_console" + if existing != "" { + val = existing + " " + val + } + return []byte(string(m[1]) + val + string(m[3])) + }) + return out +} + +func regexpContains(s, sub string) bool { + return regexp.MustCompile(regexp.QuoteMeta(sub)).MatchString(s) +} + +// RenderConfig returns base (a kata configuration.toml) with the asset-path +// fields rewritten to point at a.* . The base config carries all the +// version-matched kata settings; we only override where the assets live, so the +// config stays in sync with the kata release (the base itself is a fetched asset). +// +// Each field must already be present in base (kata's stock clh config has them); +// a missing field is an error so we fail loudly rather than boot a half-configured VM. +func RenderConfig(base []byte, a ConfigAssets) ([]byte, error) { + if a.Kernel == "" || a.Image == "" || a.Hypervisor == "" || a.Virtiofsd == "" { + return nil, fmt.Errorf("RenderConfig: all of Kernel/Image/Hypervisor/Virtiofsd are required, got %+v", a) + } + out := base + for _, f := range pathFields { + // Match a top-level `key = ` line (TOML), preserving leading + // whitespace. Anchored to line start in multiline mode. + re := regexp.MustCompile(`(?m)^(\s*` + regexp.QuoteMeta(f.key) + `\s*=\s*).*$`) + if !re.Match(out) { + return nil, fmt.Errorf("RenderConfig: key %q not found in base config", f.key) + } + out = re.ReplaceAll(out, []byte("${1}"+f.value(a))) + } + return out, nil +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/config_test.go b/cmd/ateom-cloud-hypervisor/internal/kata/config_test.go new file mode 100644 index 000000000..002e9466e --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/config_test.go @@ -0,0 +1,91 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "strings" + "testing" +) + +// stockConfig mirrors the asset-path lines of kata's clh configuration.toml +// (the rest of the file is elided; RenderConfig only touches these lines). +const stockConfig = `[hypervisor.clh] +path = "/usr/local/bin/cloud-hypervisor" +kernel = "/opt/kata/share/kata-containers/vmlinux.container" +image = "/opt/kata/share/kata-containers/kata-containers.img" +valid_hypervisor_paths = ["/opt/kata/bin/cloud-hypervisor","/usr/local/bin/cloud-hypervisor"] +shared_fs = "virtio-fs" +virtio_fs_daemon = "/usr/local/bin/virtiofsd-patched" +valid_virtio_fs_daemon_paths = ["/usr/local/bin/virtiofsd-patched","/opt/kata/libexec/virtiofsd"] +` + +func TestRenderConfig(t *testing.T) { + a := ConfigAssets{ + Kernel: "/var/lib/ateom-gvisor/static-files/vmlinux-abc", + Image: "/var/lib/ateom-gvisor/static-files/rootfs-def", + Hypervisor: "/var/lib/ateom-gvisor/static-files/cloud-hypervisor-123", + Virtiofsd: "/var/lib/ateom-gvisor/static-files/virtiofsd-456", + } + out, err := RenderConfig([]byte(stockConfig), a) + if err != nil { + t.Fatalf("RenderConfig: %v", err) + } + got := string(out) + + wantLines := []string{ + `kernel = "` + a.Kernel + `"`, + `image = "` + a.Image + `"`, + `path = "` + a.Hypervisor + `"`, + `valid_hypervisor_paths = ["` + a.Hypervisor + `"]`, + `virtio_fs_daemon = "` + a.Virtiofsd + `"`, + `valid_virtio_fs_daemon_paths = ["` + a.Virtiofsd + `"]`, + } + for _, w := range wantLines { + if !strings.Contains(got, w) { + t.Errorf("rendered config missing line %q\n--- got ---\n%s", w, got) + } + } + // Untouched settings must survive. + if !strings.Contains(got, `shared_fs = "virtio-fs"`) { + t.Error("RenderConfig dropped shared_fs") + } + // No stale /opt/kata or default cloud-hypervisor paths remain. + for _, stale := range []string{"/opt/kata/share", "/opt/kata/bin/cloud-hypervisor", "/opt/kata/libexec/virtiofsd"} { + if strings.Contains(got, stale) { + t.Errorf("rendered config still references stale path %q", stale) + } + } +} + +func TestRenderConfigMissingField(t *testing.T) { + // Base lacking virtio_fs_daemon -> error (fail loudly). + base := `kernel = "/k" +image = "/i" +path = "/p" +valid_hypervisor_paths = ["/p"] +valid_virtio_fs_daemon_paths = ["/v"] +` + _, err := RenderConfig([]byte(base), ConfigAssets{Kernel: "/k2", Image: "/i2", Hypervisor: "/p2", Virtiofsd: "/v2"}) + if err == nil { + t.Fatal("expected error for missing virtio_fs_daemon field") + } +} + +func TestRenderConfigMissingAsset(t *testing.T) { + _, err := RenderConfig([]byte(stockConfig), ConfigAssets{Kernel: "/k"}) // others empty + if err == nil { + t.Fatal("expected error for missing asset paths") + } +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/kata.go b/cmd/ateom-cloud-hypervisor/internal/kata/kata.go new file mode 100644 index 000000000..c2d50ad23 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/kata.go @@ -0,0 +1,357 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package kata drives a single containerd-shim-kata-v2 instance over ttrpc +// WITHOUT a containerd daemon. +// +// The shim uses containerd's standard runtime-v2 framework: invoking it with +// the "start" action (cwd = the OCI bundle) brings up a per-task ttrpc server +// and prints its socket address on stdout. We then speak the task v2 API +// (Create/Start/Pause/Resume/Kill/Delete/...) directly against that socket. +// +// This is the RUN half of ateom-cloud-hypervisor: kata boots the cloud- +// hypervisor micro-VM (guest kernel + rootfs + kata-agent) and runs the OCI +// container; ateom drives CH's snapshot/restore underneath (see internal/ch), +// against the api-socket kata exposes at CLHSocketPath(id). +// +// Empirically verified against kata 3.31.0 (containerd-shim-kata-v2): the +// "start" action returns "unix:///run/containerd/s/" and works with no +// containerd running. +package kata + +import ( + "context" + "fmt" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + task "github.com/containerd/containerd/api/runtime/task/v2" + "github.com/containerd/containerd/api/types" + "github.com/containerd/ttrpc" + "golang.org/x/sys/unix" +) + +const ( + // DefaultShimBinary is the kata containerd shim v2 executable. + DefaultShimBinary = "containerd-shim-kata-v2" + + // RuntimeID is the kata shim's containerd runtime identifier. + RuntimeID = "io.containerd.kata.v2" + + // vcVMDir is where kata creates per-sandbox runtime state, including the + // cloud-hypervisor API socket. + vcVMDir = "/run/vc/vm" +) + +// CLHSocketPath returns the cloud-hypervisor API socket kata creates for the +// sandbox with the given id. ateom drives CH snapshot/restore against it. +func CLHSocketPath(id string) string { + return filepath.Join(vcVMDir, id, "clh-api.sock") +} + +// Shim drives one containerd-shim-kata-v2 over ttrpc. +type Shim struct { + // Binary is the shim executable; defaults to DefaultShimBinary on PATH. + Binary string + // ID is the container/sandbox id. It also names the per-sandbox runtime + // dir, so CLHSocketPath(ID) locates the CH api-socket. + ID string + // Bundle is the OCI bundle directory (contains config.json and rootfs/). + Bundle string + // Namespace is the containerd namespace the shim runs under (e.g. "default"). + Namespace string + // GRPCAddress is the containerd grpc address the shim records. We run no + // containerd, but the shim uses it (with Namespace + ID) to derive its + // deterministic ttrpc socket path, so it must be a stable unique path. + GRPCAddress string + // TTRPCAddress is the events-publish target. With no containerd to receive + // them, kata logs publish failures but continues; point it anywhere stable. + TTRPCAddress string + // Diagnostics receives the start/delete action's stdout/stderr if set. + Diagnostics io.Writer + // Debug enables the shim's -debug flag (verbose logs to the bundle "log" fifo). + Debug bool + // ConfigFile, if set, is passed to the shim as KATA_CONF_FILE so it uses a + // generated configuration.toml (pointing at runtime-fetched assets) instead + // of the well-known /etc/kata-containers path. + ConfigFile string + + addr string + conn net.Conn + client *ttrpc.Client + task task.TTRPCTaskService + cmd *exec.Cmd // the foreground shim server process we manage +} + +func (s *Shim) binary() string { + if s.Binary != "" { + return s.Binary + } + return DefaultShimBinary +} + +func (s *Shim) shimEnv() []string { + env := append(os.Environ(), + "TTRPC_ADDRESS="+s.TTRPCAddress, + "GRPC_ADDRESS="+s.GRPCAddress, + "NAMESPACE="+s.Namespace, + ) + if s.ConfigFile != "" { + env = append(env, "KATA_CONF_FILE="+s.ConfigFile) + } + return env +} + +// Bootstrap launches the shim's ttrpc server in the FOREGROUND (on an explicit +// -socket) and connects to it. After Bootstrap the shim is up but no VM exists +// yet; call Create to boot the micro-VM and create the container. +// +// We deliberately do NOT use the containerd "start" action: it double-forks a +// detached daemon that dies silently in a minimal pod (PID 1 = ateom, no +// containerd). Running the server in the foreground as a child we manage is the +// path that works in-pod, and lets ateom own its lifecycle. +func (s *Shim) Bootstrap(ctx context.Context) error { + if s.ID == "" || s.Bundle == "" { + return fmt.Errorf("kata.Shim requires ID and Bundle") + } + + // The socket dir is containerd convention; create it (no containerd does). + if err := os.MkdirAll(shimSocketDir, 0o711); err != nil { + return fmt.Errorf("while creating shim socket dir %q: %w", shimSocketDir, err) + } + socketPath := filepath.Join(shimSocketDir, "ateomchv-"+s.ID+".sock") + _ = os.Remove(socketPath) + + // The shim opens /log (O_WRONLY) for its logrus output and blocks + // until a reader appears; create the fifo and drain it, or the server hangs + // during logging setup and never binds its socket. + if err := s.startLogDrain(); err != nil { + return err + } + + args := []string{ + "-namespace", s.Namespace, + "-address", s.GRPCAddress, + "-id", s.ID, + } + if s.Debug { + args = append(args, "-debug") + } + args = append(args, "-socket", socketPath) + + // Deliberately NOT exec.CommandContext: the shim must outlive the + // RunWorkload RPC whose ctx spawned it (gRPC cancels the ctx when the + // handler returns, and CommandContext would then SIGKILL the shim under a + // healthy running actor). Lifetime is managed via s.cmd (Close kills it); + // the dial loop below honors ctx for bootstrap cancellation. + cmd := exec.Command(s.binary(), args...) + cmd.Dir = s.Bundle + cmd.Env = s.shimEnv() + cmd.Stdout = s.Diagnostics + cmd.Stderr = s.Diagnostics + if err := cmd.Start(); err != nil { + return fmt.Errorf("while starting shim server: %w", err) + } + s.cmd = cmd + s.addr = socketPath + + // Wait for the server to bind + connect. + deadline := time.Now().Add(15 * time.Second) + var conn net.Conn + var err error + for { + conn, err = net.DialTimeout("unix", socketPath, 2*time.Second) + if err == nil { + break + } + if !time.Now().Before(deadline) { + _ = cmd.Process.Kill() + return fmt.Errorf("while dialing shim ttrpc socket %q: %w", socketPath, err) + } + select { + case <-ctx.Done(): + _ = cmd.Process.Kill() + return ctx.Err() + case <-time.After(50 * time.Millisecond): + } + } + s.conn = conn + s.client = ttrpc.NewClient(conn) + s.task = task.NewTTRPCTaskClient(s.client) + return nil +} + +// shimSocketDir is where the kata shim binds its ttrpc socket (containerd +// convention: /run/containerd/s/). +const shimSocketDir = "/run/containerd/s" + +// startLogDrain creates the /log fifo the shim writes its logs to and +// drains it (to Diagnostics, else discarded). The shim opens it O_WRONLY and +// blocks until a reader exists, so this must run before launching the server. +func (s *Shim) startLogDrain() error { + logFifo := filepath.Join(s.Bundle, "log") + _ = os.Remove(logFifo) + if err := unix.Mkfifo(logFifo, 0o700); err != nil { + return fmt.Errorf("while creating shim log fifo: %w", err) + } + go func() { + f, err := os.OpenFile(logFifo, os.O_RDONLY, 0) + if err != nil { + return + } + defer f.Close() + dst := io.Writer(io.Discard) + if s.Diagnostics != nil { + dst = s.Diagnostics + } + _, _ = io.Copy(dst, f) + }() + return nil +} + +// Address returns the shim's ttrpc socket path (after Bootstrap). +func (s *Shim) Address() string { return s.addr } + +// CreateOptions configures the container/task creation. +type CreateOptions struct { + // Rootfs are mounts the shim should set up at /rootfs. Leave nil + // when the bundle's rootfs/ is already populated (atelet's model). + Rootfs []*types.Mount + // Stdin/Stdout/Stderr are fifo or file paths for the container's stdio. + // Empty discards. + Stdin string + Stdout string + Stderr string + // Terminal requests a pty for the container's process. + Terminal bool +} + +// Create boots the micro-VM and creates the container from the bundle. Returns +// the task pid (the in-VMM process as seen by the shim). +func (s *Shim) Create(ctx context.Context, o CreateOptions) (uint32, error) { + resp, err := s.task.Create(ctx, &task.CreateTaskRequest{ + ID: s.ID, + Bundle: s.Bundle, + Rootfs: o.Rootfs, + Terminal: o.Terminal, + Stdin: o.Stdin, + Stdout: o.Stdout, + Stderr: o.Stderr, + }) + if err != nil { + return 0, fmt.Errorf("while calling task.Create: %w", err) + } + return resp.GetPid(), nil +} + +// Start starts the created container's init process. +func (s *Shim) Start(ctx context.Context) (uint32, error) { + resp, err := s.task.Start(ctx, &task.StartRequest{ID: s.ID}) + if err != nil { + return 0, fmt.Errorf("while calling task.Start: %w", err) + } + return resp.GetPid(), nil +} + +// Pause pauses the container (quiesce before snapshot). +func (s *Shim) Pause(ctx context.Context) error { + if _, err := s.task.Pause(ctx, &task.PauseRequest{ID: s.ID}); err != nil { + return fmt.Errorf("while calling task.Pause: %w", err) + } + return nil +} + +// Resume resumes a paused container. +func (s *Shim) Resume(ctx context.Context) error { + if _, err := s.task.Resume(ctx, &task.ResumeRequest{ID: s.ID}); err != nil { + return fmt.Errorf("while calling task.Resume: %w", err) + } + return nil +} + +// Kill sends a signal to the container. If all is true, signals every process. +func (s *Shim) Kill(ctx context.Context, signal uint32, all bool) error { + if _, err := s.task.Kill(ctx, &task.KillRequest{ID: s.ID, Signal: signal, All: all}); err != nil { + return fmt.Errorf("while calling task.Kill: %w", err) + } + return nil +} + +// Delete deletes the container/task (after it has stopped). +func (s *Shim) Delete(ctx context.Context) error { + if _, err := s.task.Delete(ctx, &task.DeleteRequest{ID: s.ID}); err != nil { + return fmt.Errorf("while calling task.Delete: %w", err) + } + return nil +} + +// Shutdown stops the shim (and its VMM). Pass now=true to force. +func (s *Shim) Shutdown(ctx context.Context, now bool) error { + if s.task == nil { + return nil + } + if _, err := s.task.Shutdown(ctx, &task.ShutdownRequest{ID: s.ID, Now: now}); err != nil { + return fmt.Errorf("while calling task.Shutdown: %w", err) + } + return nil +} + +// Close closes the ttrpc connection and stops the foreground shim server we +// launched (which also tears down its VMM). +func (s *Shim) Close() error { + var firstErr error + if s.client != nil { + if err := s.client.Close(); err != nil { + firstErr = err + } + } + if s.cmd != nil && s.cmd.Process != nil { + _ = s.cmd.Process.Kill() + _, _ = s.cmd.Process.Wait() + } + return firstErr +} + +// CleanupAction runs the shim's "delete" action, which tears down any leftover +// sandbox state for ID. Best-effort; safe to call after Shutdown. +// +// After a successful Shutdown the per-sandbox state dir is already gone, so the +// "delete" action reports "/run/vc/sbs/: no such file or directory" and +// exits non-zero; that case is treated as success (nothing left to clean). +func (s *Shim) CleanupAction(ctx context.Context) error { + cmd := exec.CommandContext(ctx, s.binary(), + "-namespace", s.Namespace, + "-address", s.GRPCAddress, + "-id", s.ID, + "delete", + ) + cmd.Dir = s.Bundle + cmd.Env = s.shimEnv() + out, err := cmd.CombinedOutput() + if s.Diagnostics != nil { + fmt.Fprintf(s.Diagnostics, "shim delete: %q (err=%v)\n", string(out), err) + } + if err != nil { + if strings.Contains(string(out), "no such file or directory") { + return nil // already torn down + } + return fmt.Errorf("while running shim delete: %w (%s)", err, strings.TrimSpace(string(out))) + } + return nil +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/kata_integration_test.go b/cmd/ateom-cloud-hypervisor/internal/kata/kata_integration_test.go new file mode 100644 index 000000000..8d93d07c0 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/kata_integration_test.go @@ -0,0 +1,283 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + task "github.com/containerd/containerd/api/runtime/task/v2" + tasktypes "github.com/containerd/containerd/api/types/task" + "golang.org/x/sys/unix" +) + +// TestKataLifecycle drives a real containerd-shim-kata-v2 (no containerd) through +// the full RUN lifecycle in a cloud-hypervisor micro-VM, then pause/resume, and +// tears it down. It requires a host with kata + /dev/kvm and is gated behind +// KATA_INTEGRATION=1. +// +// Env: +// +// KATA_INTEGRATION=1 enable the test +// KATA_SHIM= shim binary (default /opt/kata/bin/containerd-shim-kata-v2) +// KATA_ROOTFS_SRC= a populated rootfs to copy into the bundle (e.g. an +// extracted busybox image). Required. +// KATA_STDIO=1 wire container stdout/stderr to files in the bundle. +// +// Run as root on a KVM-capable Linux host with kata installed, e.g.: +// +// sudo KATA_INTEGRATION=1 KATA_ROOTFS_SRC=/tmp/bb/rootfs ./kata.test -test.v -test.run Lifecycle +func TestKataLifecycle(t *testing.T) { + if os.Getenv("KATA_INTEGRATION") != "1" { + t.Skip("set KATA_INTEGRATION=1 to run (requires kata + /dev/kvm)") + } + rootfsSrc := os.Getenv("KATA_ROOTFS_SRC") + if rootfsSrc == "" { + t.Fatal("KATA_ROOTFS_SRC is required (a populated rootfs dir to copy into the bundle)") + } + shim := os.Getenv("KATA_SHIM") + if shim == "" { + shim = "/opt/kata/bin/containerd-shim-kata-v2" + } + + id := fmt.Sprintf("ateomchv-it-%d", os.Getpid()) + work := filepath.Join("/tmp", id) + bundle := filepath.Join(work, "bundle") + rootfs := filepath.Join(bundle, "rootfs") + if err := os.MkdirAll(rootfs, 0o755); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.RemoveAll(work) }) + + // Populate the bundle rootfs (atelet's model: a pre-populated rootfs dir). + if out, err := exec.Command("cp", "-a", rootfsSrc+"/.", rootfs+"/").CombinedOutput(); err != nil { + t.Fatalf("copying rootfs: %v: %s", err, out) + } + writeBundleConfig(t, bundle, id) + + // containerd creates a "log" fifo in the bundle and reads it; the kata shim + // opens it O_WRONLY for its logrus output (and blocks if nobody reads). + // Provide it and drain to t.Log so we can see why the shim behaves as it does. + logFifo := filepath.Join(bundle, "log") + if err := unix.Mkfifo(logFifo, 0o700); err != nil { + t.Fatalf("mkfifo log: %v", err) + } + go func() { + f, err := os.OpenFile(logFifo, os.O_RDONLY, 0) + if err != nil { + return + } + defer f.Close() + buf := make([]byte, 4096) + for { + n, err := f.Read(buf) + if n > 0 { + t.Logf("[shim-log] %s", buf[:n]) + } + if err != nil { + return + } + } + }() + + var createOpts CreateOptions + if os.Getenv("KATA_STDIO") == "1" { + createOpts.Stdout = filepath.Join(work, "stdout") + createOpts.Stderr = filepath.Join(work, "stderr") + } + + s := &Shim{ + Binary: shim, + ID: id, + Bundle: bundle, + Namespace: "default", + GRPCAddress: filepath.Join(work, "fake-containerd.sock"), + TTRPCAddress: filepath.Join(work, "fake-containerd.sock.ttrpc"), + Diagnostics: testWriter{t}, + Debug: true, + ConfigFile: os.Getenv("KATA_CONF_FILE"), // optional: test a generated config + } + + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + // Always attempt teardown so a failure mid-test doesn't leak a VMM. + defer func() { + _ = s.Kill(ctx, 9, true) + shutCtx, c := context.WithTimeout(context.Background(), 15*time.Second) + _ = s.Shutdown(shutCtx, true) + c() + _ = s.Close() + clCtx, c2 := context.WithTimeout(context.Background(), 15*time.Second) + _ = s.CleanupAction(clCtx) + c2() + }() + + if err := s.Bootstrap(ctx); err != nil { + t.Fatalf("Bootstrap: %v", err) + } + t.Logf("shim ttrpc address: %s", s.Address()) + + pid, err := s.Create(ctx, createOpts) + if err != nil { + t.Fatalf("Create: %v", err) + } + t.Logf("Create ok, pid=%d", pid) + + // The CH api-socket should now exist for this sandbox id. + clh := CLHSocketPath(id) + if !waitFor(5*time.Second, func() bool { _, err := os.Stat(clh); return err == nil }) { + t.Errorf("clh api-socket %q did not appear", clh) + } else { + t.Logf("clh api-socket present: %s", clh) + } + + if _, err := s.Start(ctx); err != nil { + t.Fatalf("Start: %v", err) + } + t.Log("Start ok") + + assertStatus(t, ctx, s, tasktypes.Status_RUNNING) + + if err := s.Pause(ctx); err != nil { + t.Fatalf("Pause: %v", err) + } + assertStatus(t, ctx, s, tasktypes.Status_PAUSED) + t.Log("Pause ok") + + if err := s.Resume(ctx); err != nil { + t.Fatalf("Resume: %v", err) + } + assertStatus(t, ctx, s, tasktypes.Status_RUNNING) + t.Log("Resume ok") + + // Graceful-ish: kill the container, then delete, then shutdown (defer also + // force-cleans). + if err := s.Kill(ctx, 9, true); err != nil { + t.Errorf("Kill: %v", err) + } + time.Sleep(time.Second) + if err := s.Delete(ctx); err != nil { + t.Logf("Delete (non-fatal): %v", err) + } + t.Log("lifecycle complete") +} + +func assertStatus(t *testing.T, ctx context.Context, s *Shim, want tasktypes.Status) { + t.Helper() + resp, err := s.task.State(ctx, &task.StateRequest{ID: s.ID}) + if err != nil { + t.Fatalf("State: %v", err) + } + if resp.GetStatus() != want { + t.Errorf("status = %v, want %v", resp.GetStatus(), want) + } +} + +func waitFor(d time.Duration, cond func() bool) bool { + end := time.Now().Add(d) + for time.Now().Before(end) { + if cond() { + return true + } + time.Sleep(50 * time.Millisecond) + } + return cond() +} + +type testWriter struct{ t *testing.T } + +func (w testWriter) Write(p []byte) (int, error) { + w.t.Logf("%s", p) + return len(p), nil +} + +// writeBundleConfig emits an OCI spec mirroring what `ctr run --runtime +// io.containerd.kata.v2` produces (the proven-good shape). kata's OCI conversion +// nil-derefs without linux.resources, so a too-minimal spec crashes the shim. +func writeBundleConfig(t *testing.T, bundle, id string) { + t.Helper() + caps := []string{ + "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER", "CAP_MKNOD", + "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID", "CAP_SETFCAP", "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT", "CAP_KILL", "CAP_AUDIT_WRITE", + } + spec := map[string]any{ + "ociVersion": "1.2.0", + "process": map[string]any{ + "user": map[string]any{"uid": 0, "gid": 0, "additionalGids": []int{0, 10}}, + "args": []string{"sleep", "3600"}, + "env": []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + "cwd": "/", + "capabilities": map[string]any{ + "bounding": caps, "effective": caps, "permitted": caps, + }, + "rlimits": []map[string]any{{"type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024}}, + "noNewPrivileges": true, + }, + "root": map[string]any{"path": "rootfs"}, + "mounts": []map[string]any{ + {"destination": "/proc", "type": "proc", "source": "proc", "options": []string{"nosuid", "noexec", "nodev"}}, + {"destination": "/dev", "type": "tmpfs", "source": "tmpfs", "options": []string{"nosuid", "strictatime", "mode=755", "size=65536k"}}, + {"destination": "/dev/pts", "type": "devpts", "source": "devpts", "options": []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}}, + {"destination": "/dev/shm", "type": "tmpfs", "source": "shm", "options": []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}}, + {"destination": "/dev/mqueue", "type": "mqueue", "source": "mqueue", "options": []string{"nosuid", "noexec", "nodev"}}, + {"destination": "/sys", "type": "sysfs", "source": "sysfs", "options": []string{"nosuid", "noexec", "nodev", "ro"}}, + {"destination": "/run", "type": "tmpfs", "source": "tmpfs", "options": []string{"nosuid", "strictatime", "mode=755", "size=65536k"}}, + }, + "linux": map[string]any{ + "resources": map[string]any{ + "devices": []map[string]any{ + {"allow": false, "access": "rwm"}, + {"allow": true, "type": "c", "major": 1, "minor": 3, "access": "rwm"}, + {"allow": true, "type": "c", "major": 1, "minor": 8, "access": "rwm"}, + {"allow": true, "type": "c", "major": 1, "minor": 7, "access": "rwm"}, + {"allow": true, "type": "c", "major": 5, "minor": 0, "access": "rwm"}, + {"allow": true, "type": "c", "major": 1, "minor": 5, "access": "rwm"}, + {"allow": true, "type": "c", "major": 1, "minor": 9, "access": "rwm"}, + {"allow": true, "type": "c", "major": 5, "minor": 1, "access": "rwm"}, + {"allow": true, "type": "c", "major": 136, "access": "rwm"}, + {"allow": true, "type": "c", "major": 5, "minor": 2, "access": "rwm"}, + }, + "cpu": map[string]any{"shares": 1024}, + }, + "cgroupsPath": "/ateomchv/" + id, + "namespaces": []map[string]any{ + {"type": "pid"}, + {"type": "ipc"}, + {"type": "uts"}, + {"type": "mount"}, + {"type": "network"}, + }, + "maskedPaths": []string{"/proc/acpi", "/proc/asound", "/proc/kcore", "/proc/keys", "/proc/latency_stats", "/proc/timer_list", "/proc/timer_stats", "/proc/sched_debug", "/sys/firmware", "/sys/devices/virtual/powercap", "/proc/scsi"}, + "readonlyPaths": []string{"/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger"}, + }, + } + b, err := json.MarshalIndent(spec, "", " ") + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(bundle, "config.json"), b, 0o644); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/restore.go b/cmd/ateom-cloud-hypervisor/internal/kata/restore.go new file mode 100644 index 000000000..e69a2bffa --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/restore.go @@ -0,0 +1,211 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" +) + +// Per-sandbox runtime paths kata uses. The CH snapshot's config.json references +// these absolute paths (virtiofsd vhost-user socket, vsock socket), so restore +// must recreate them (or rewrite config.json) for the sandbox id. + +// VMDir is the per-sandbox runtime dir kata creates (holds clh-api.sock, +// clh.sock, virtiofsd.sock). +func VMDir(id string) string { return filepath.Join(vcVMDir, id) } + +// VirtiofsdSocketPath is the vhost-user-fs socket the CH snapshot's _fs1 device +// references; restore must start virtiofsd listening here. +func VirtiofsdSocketPath(id string) string { return filepath.Join(VMDir(id), "virtiofsd.sock") } + +// VsockSocketPath is the hybrid-vsock socket the CH snapshot's vsock device +// references; CH recreates the listener here on restore. +func VsockSocketPath(id string) string { return filepath.Join(VMDir(id), "clh.sock") } + +// SharedDir is the host directory kata virtio-fs-shares into the guest (the +// container rootfs lives here, as an overlay mount inside the shim's mount +// namespace). Its CONTENTS must be captured (CaptureSharedDir) at checkpoint and +// reconstructed (ReconstructSharedDir) at restore for find-paths to re-open them. +func SharedDir(id string) string { + return filepath.Join("/run/kata-containers/shared/sandboxes", id, "shared") +} + +// ShimPID returns the pid of the running shim daemon for this sandbox. The shim +// writes it to /shim.pid; falls back to scanning /proc for a shim +// process whose cmdline names this sandbox id (no external pgrep dependency). +func (s *Shim) ShimPID() (int, error) { + // In the foreground-server model the shim is our own child process, so its + // pid is authoritative (the shim does not write shim.pid and a /proc scan can + // miss it). Prefer it. + if s.cmd != nil && s.cmd.Process != nil && s.cmd.Process.Pid > 0 { + return s.cmd.Process.Pid, nil + } + if b, err := os.ReadFile(filepath.Join(s.Bundle, "shim.pid")); err == nil { + if pid, perr := strconv.Atoi(strings.TrimSpace(string(b))); perr == nil && pid > 0 { + return pid, nil + } + } + entries, err := os.ReadDir("/proc") + if err != nil { + return 0, fmt.Errorf("scanning /proc for shim pid: %w", err) + } + for _, e := range entries { + pid, err := strconv.Atoi(e.Name()) + if err != nil { + continue // not a pid dir + } + cmdline, err := os.ReadFile(filepath.Join("/proc", e.Name(), "cmdline")) + if err != nil { + continue + } + // cmdline args are NUL-separated. + args := string(cmdline) + if strings.Contains(args, "containerd-shim-kata-v2") && strings.Contains(args, s.ID) { + return pid, nil + } + } + return 0, fmt.Errorf("no shim process found for %q", s.ID) +} + +// CaptureSharedDir tars the sandbox's virtio-fs shared dir (the merged container +// rootfs) from INSIDE the shim's mount namespace into destTar. The content is +// only visible in the shim's mountns, so we nsenter into it; tar streams to +// stdout (a host file) to avoid path-in-namespace ambiguity. +// +// The guest must be paused first (so the rootfs is quiescent). +func (s *Shim) CaptureSharedDir(ctx context.Context, destTar string) error { + pid, err := s.ShimPID() + if err != nil { + return err + } + f, err := os.Create(destTar) + if err != nil { + return fmt.Errorf("creating %q: %w", destTar, err) + } + defer f.Close() + + cmd := exec.CommandContext(ctx, "nsenter", "-m", "-t", strconv.Itoa(pid), + "tar", "-C", SharedDir(s.ID), "-cf", "-", ".") + cmd.Stdout = f + var stderr strings.Builder + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("capturing shared dir via nsenter tar: %w (%s)", err, strings.TrimSpace(stderr.String())) + } + return nil +} + +// CaptureSharedDirLocal tars the shared dir for id from the CURRENT mount +// namespace into destTar. Use for ateom-owned (restored) actors, whose shared +// dir was built by ReconstructSharedDir in ateom's own namespace (no shim to +// nsenter). The guest must be paused first. +func CaptureSharedDirLocal(ctx context.Context, id, destTar string) error { + f, err := os.Create(destTar) + if err != nil { + return fmt.Errorf("creating %q: %w", destTar, err) + } + defer f.Close() + cmd := exec.CommandContext(ctx, "tar", "-C", SharedDir(id), "-cf", "-", ".") + cmd.Stdout = f + var stderr strings.Builder + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("capturing local shared dir: %w (%s)", err, strings.TrimSpace(stderr.String())) + } + return nil +} + +// ReconstructSharedDir extracts a CaptureSharedDir tar into the shared dir for +// id (a plain host directory; find-paths re-opens by relative path so this need +// not be a real overlay mount). +func ReconstructSharedDir(ctx context.Context, srcTar, id string) error { + dst := SharedDir(id) + if err := os.RemoveAll(dst); err != nil { + return fmt.Errorf("clearing shared dir %q: %w", dst, err) + } + if err := os.MkdirAll(dst, 0o700); err != nil { + return fmt.Errorf("creating shared dir %q: %w", dst, err) + } + cmd := exec.CommandContext(ctx, "tar", "-C", dst, "-xf", srcTar) + var stderr strings.Builder + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("reconstructing shared dir: %w (%s)", err, strings.TrimSpace(stderr.String())) + } + return nil +} + +// VirtiofsdOptions configures StartVirtiofsd. +type VirtiofsdOptions struct { + // Binary is the virtiofsd executable; defaults to "virtiofsd". + Binary string + // SocketPath is the vhost-user socket CH will connect to (VirtiofsdSocketPath). + SocketPath string + // SharedDir is the directory to serve (SharedDir(id)). + SharedDir string + // Log receives virtiofsd's stdout/stderr. + Log io.Writer +} + +// StartVirtiofsd launches virtiofsd in find-paths migration mode, +// matching the args kata uses, and waits until its socket appears. The returned +// cmd is owned by the caller (Kill on teardown). find-paths is required so the +// CH snapshot's vhost-user device-state reload (re-open by path) succeeds. +func StartVirtiofsd(ctx context.Context, o VirtiofsdOptions) (*exec.Cmd, error) { + bin := o.Binary + if bin == "" { + bin = "virtiofsd" + } + _ = os.Remove(o.SocketPath) + // Deliberately NOT exec.CommandContext: virtiofsd must outlive the Restore + // RPC whose ctx launched it (gRPC cancels the ctx when the handler returns, + // which would SIGKILL the vhost-user backend under the restored VM). The + // caller owns the returned cmd; the wait loop below honors ctx. + cmd := exec.Command(bin, + "--socket-path="+o.SocketPath, + "--shared-dir="+o.SharedDir, + "--cache=auto", + "--thread-pool-size=1", + "--announce-submounts", + "--migration-mode", "find-paths", + ) + cmd.Stdout = o.Log + cmd.Stderr = o.Log + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("starting virtiofsd: %w", err) + } + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + if _, err := os.Stat(o.SocketPath); err == nil { + return cmd, nil + } + select { + case <-ctx.Done(): + _ = cmd.Process.Kill() + return nil, ctx.Err() + case <-time.After(50 * time.Millisecond): + } + } + _ = cmd.Process.Kill() + return nil, fmt.Errorf("virtiofsd socket %q did not appear", o.SocketPath) +} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/restore_integration_test.go b/cmd/ateom-cloud-hypervisor/internal/kata/restore_integration_test.go new file mode 100644 index 000000000..bf66042ec --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/internal/kata/restore_integration_test.go @@ -0,0 +1,186 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kata + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/ch" + "golang.org/x/sys/unix" +) + +// TestKataRestore exercises the full restore cycle against real kata + CH: +// boot a busybox CH micro-VM via the shim, capture the virtio-fs shared dir +// from the shim's mount namespace, pause+snapshot, tear the original down, then +// RESTORE by reconstructing the shared dir + relaunching virtiofsd + CH +// --restore + resume. A post-restore pause/resume roundtrip proves the restored +// guest is live. Gated behind KATA_INTEGRATION=1. +// +// Env (in addition to KATA_INTEGRATION / KATA_ROOTFS_SRC / KATA_SHIM): +// +// KATA_CH= cloud-hypervisor (default /usr/local/bin/cloud-hypervisor) +// KATA_VIRTIOFSD= virtiofsd with migration support (default /usr/local/bin/virtiofsd-patched) +func TestKataRestore(t *testing.T) { + if os.Getenv("KATA_INTEGRATION") != "1" { + t.Skip("set KATA_INTEGRATION=1 to run (requires kata + /dev/kvm)") + } + rootfsSrc := os.Getenv("KATA_ROOTFS_SRC") + if rootfsSrc == "" { + t.Fatal("KATA_ROOTFS_SRC is required") + } + shim := envOr("KATA_SHIM", "/opt/kata/bin/containerd-shim-kata-v2") + chBin := envOr("KATA_CH", "/usr/local/bin/cloud-hypervisor") + vfsdBin := envOr("KATA_VIRTIOFSD", "/usr/local/bin/virtiofsd-patched") + + id := fmt.Sprintf("ateomchv-rst-%d", os.Getpid()) + work := filepath.Join("/tmp", id) + bundle := filepath.Join(work, "bundle") + rootfs := filepath.Join(bundle, "rootfs") + if err := os.MkdirAll(rootfs, 0o755); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.RemoveAll(work) }) + if out, err := exec.Command("cp", "-a", rootfsSrc+"/.", rootfs+"/").CombinedOutput(); err != nil { + t.Fatalf("copying rootfs: %v: %s", err, out) + } + writeBundleConfig(t, bundle, id) + drainLogFifo(t, bundle) + + s := &Shim{ + Binary: shim, + ID: id, + Bundle: bundle, + Namespace: "default", + GRPCAddress: filepath.Join(work, "fake-containerd.sock"), + TTRPCAddress: filepath.Join(work, "fake-containerd.sock.ttrpc"), + Diagnostics: testWriter{t}, + Debug: true, + } + + ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second) + defer cancel() + + var vfsdCmd, chCmd *exec.Cmd + t.Cleanup(func() { + for _, c := range []*exec.Cmd{chCmd, vfsdCmd} { + if c != nil && c.Process != nil { + _ = c.Process.Kill() + } + } + _ = os.RemoveAll(VMDir(id)) + _ = os.RemoveAll(SharedDir(id)) + }) + + // --- Boot the original incarnation. --- + if err := s.Bootstrap(ctx); err != nil { + t.Fatalf("Bootstrap: %v", err) + } + if _, err := s.Create(ctx, CreateOptions{}); err != nil { + t.Fatalf("Create: %v", err) + } + if _, err := s.Start(ctx); err != nil { + t.Fatalf("Start: %v", err) + } + t.Log("original micro-VM running") + + // --- Capture shared dir (from the shim mountns) + snapshot. --- + tarPath := filepath.Join(work, "shared-dir.tar") + if err := s.CaptureSharedDir(ctx, tarPath); err != nil { + t.Fatalf("CaptureSharedDir: %v", err) + } + t.Logf("captured shared dir -> %s", tarPath) + + client := ch.NewClient(CLHSocketPath(id)) + if err := client.WaitReady(ctx, 10*time.Second); err != nil { + t.Fatalf("CH WaitReady: %v", err) + } + if err := client.Pause(ctx); err != nil { + t.Fatalf("Pause: %v", err) + } + snapDir := filepath.Join(work, "snapshot") + if err := client.Snapshot(ctx, snapDir); err != nil { + t.Fatalf("Snapshot: %v", err) + } + t.Logf("snapshot -> %s", snapDir) + + // --- Tear the original down fast (kill CH + shim; skip agent-pinging stop). --- + _ = client.Shutdown(ctx) + if pid, err := s.ShimPID(); err == nil { + _ = unix.Kill(pid, unix.SIGKILL) + } + _ = s.Close() + time.Sleep(time.Second) + + // --- RESTORE (same id; bypasses the shim). --- + if err := ReconstructSharedDir(ctx, tarPath, id); err != nil { + t.Fatalf("ReconstructSharedDir: %v", err) + } + if err := os.MkdirAll(VMDir(id), 0o700); err != nil { + t.Fatal(err) + } + vfsdCmd, err := StartVirtiofsd(ctx, VirtiofsdOptions{ + Binary: vfsdBin, + SocketPath: VirtiofsdSocketPath(id), + SharedDir: SharedDir(id), + Log: testWriter{t}, + }) + if err != nil { + t.Fatalf("StartVirtiofsd: %v", err) + } + + var rclient *ch.Client + chCmd, rclient, err = ch.Restore(ctx, ch.RestoreOptions{ + Binary: chBin, + APISocket: filepath.Join(VMDir(id), "clh-api-restore.sock"), + SourceDir: snapDir, + Stdout: testWriter{t}, + Stderr: testWriter{t}, + }) + if err != nil { + t.Fatalf("ch.Restore: %v", err) + } + t.Log("CH restored from snapshot") + + if err := rclient.Resume(ctx); err != nil { + t.Fatalf("Resume after restore: %v", err) + } + + // Liveness: a pause/resume roundtrip on the restored VM proves it is live. + if err := rclient.Pause(ctx); err != nil { + t.Errorf("post-restore Pause (liveness): %v", err) + } + if err := rclient.Resume(ctx); err != nil { + t.Errorf("post-restore Resume (liveness): %v", err) + } + t.Log("RESTORE OK: guest resumed and is live on a fresh CH process") + + // Teardown the restored incarnation. + _ = rclient.Shutdown(ctx) +} + +func envOr(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} diff --git a/cmd/ateom-cloud-hypervisor/main.go b/cmd/ateom-cloud-hypervisor/main.go new file mode 100644 index 000000000..bec8d68c3 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/main.go @@ -0,0 +1,270 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Command ateom-cloud-hypervisor is the kata + cloud-hypervisor micro-VM +// implementation of the ateompb.Ateom service, a peer to cmd/ateom-gvisor. +// +// It runs a substrate actor as a cloud-hypervisor micro-VM (launched via the +// kata guest model) and supports full suspend/resume by driving CH's native +// snapshot/restore underneath (see internal/ch). +package main + +import ( + "context" + "flag" + "fmt" + "log/slog" + "net" + "os" + "strings" + "sync" + + "github.com/agent-substrate/substrate/internal/ateinterceptors" + "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/agent-substrate/substrate/internal/proto/ateompb" + "github.com/agent-substrate/substrate/internal/serverboot" + "github.com/agent-substrate/substrate/internal/version" + "github.com/hashicorp/go-reap" + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "golang.org/x/sys/unix" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +var ( + podUID = flag.String("pod-uid", "", "The UID of the current pod") + + shimBinary = flag.String("shim-binary", "containerd-shim-kata-v2", "Path to the kata containerd shim v2 binary.") + + chBinary = flag.String("cloud-hypervisor-binary", "cloud-hypervisor", "Path to the cloud-hypervisor binary (used to relaunch on restore).") + + virtiofsdBinary = flag.String("virtiofsd-binary", "virtiofsd", "Path to the virtiofsd binary (needs find-paths migration support, >= 1.13; used on restore).") + + kataConfig = flag.String("kata-config", "", "Path to a kata configuration.toml (passed to the shim as KATA_CONF_FILE). Empty uses kata's default. atelet generates one pointing at runtime-fetched assets.") + + kataNamespace = flag.String("kata-namespace", "default", "containerd namespace the kata shim runs under.") + + kataDebug = flag.Bool("kata-debug", false, "Verbose kata debugging: shim -debug, hypervisor/agent/runtime enable_debug, and guest console (incl. kata-agent logs) forwarded into pod logs.") + + showVersion = flag.Bool("version", false, "Print version and exit.") + + // reapLock guards subprocess exec against the child reaper, mirroring + // ateom-gvisor. ateom-cloud-hypervisor will spawn the kata shim and CH + // processes under it in later milestones. + reapLock sync.RWMutex +) + +func main() { + flag.Parse() + if *showVersion { + fmt.Println(version.String()) + return + } + ctx := context.Background() + + if err := do(ctx); err != nil { + slog.ErrorContext(ctx, "Error while executing", slog.Any("err", err)) + os.Exit(1) + } +} + +func do(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + serverboot.InitLogger() + slog.InfoContext(ctx, "ateom-cloud-hypervisor booting", slog.String("version", version.String())) + + tp, err := serverboot.InitTracing(ctx, serverboot.TracingOptions{ + ServiceName: "ateom-cloud-hypervisor", + Sampler: sdktrace.ParentBased(sdktrace.NeverSample()), + // The micro-VM keeps the pod network, but for the POC we have no trace + // collector to export to; skip the exporter like ateom-gvisor. + NoExporter: true, + }) + if err != nil { + serverboot.Fatal(ctx, "Failed to initialize tracing", err) + } + defer serverboot.ShutdownProvider("TracerProvider", tp.Shutdown) + + // Create ateom dir. + ateomDir := ateompath.AteomPath(*podUID) + if err := os.MkdirAll(ateomDir, 0o700); err != nil { + return fmt.Errorf("in os.MkdirAll(%q): %w", ateomDir, err) + } + + // Reap children reparented to us (kata shim / cloud-hypervisor in later + // milestones), guarded so our own exec.Cmd calls can take the wait. + go reap.ReapChildren(nil, nil, nil, &reapLock) + slog.InfoContext(ctx, "Child process reaper launched") + + // kata launches virtiofsd with --syslog, which delivers to /dev/log. In a + // pod there is no syslog daemon, so provide a sink or virtiofsd fails + // ("Unix syslog delivery error") and Create fails. + startSyslogSink(ctx) + + // kata's virtio-fs sharing depends on mount propagation: it slave-binds + // .../shared (served by virtiofsd) from .../mounts and expects the later + // per-container rootfs bind under mounts/ to propagate across. That only + // works if the underlying mount is SHARED. On a host systemd makes / + // rshared, but a container rootfs is rprivate (runc default), so the + // propagation silently never happens: the guest sees an empty rootfs and + // createContainer fails ENOENT. Self-bind /run/kata-containers and mark it + // rshared so kata's propagation chain works inside the pod. + if err := ensureSharedPropagation(ctx, "/run/kata-containers"); err != nil { + return fmt.Errorf("while making /run/kata-containers a shared mount: %w", err) + } + + // Clean up any old socket. + sockPath := ateompath.AteomSocketPath(*podUID) + if err := os.RemoveAll(sockPath); err != nil { + return fmt.Errorf("while removing %q: %w", sockPath, err) + } + + lis, err := net.Listen("unix", sockPath) + if err != nil { + return fmt.Errorf("while opening unix socket: %w", err) + } + + // Networking (mirrors ateom-gvisor): scrape the pod's eth0, then create a + // named interior netns. RunWorkload moves eth0 into it (addrs/routes restored) + // and points kata at it so the micro-VM gets the pod's network. + eth0Link, err := netlink.LinkByName("eth0") + if err != nil { + return fmt.Errorf("while getting netlink link for eth0: %w", err) + } + eth0LinkInfo, err := scrapeLink(eth0Link) + if err != nil { + return fmt.Errorf("while scraping info from eth0: %w", err) + } + slog.InfoContext(ctx, "Scraped eth0", slog.Any("eth0", eth0LinkInfo)) + interiorNetNS, err := createNetNSWithoutSwitching(ateompath.AteomNetNSName(*podUID)) + if err != nil { + return fmt.Errorf("while creating interior netns: %w", err) + } + + svr := grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler()), + grpc.UnaryInterceptor(ateinterceptors.ServerUnaryInterceptor), + ) + ateompb.RegisterAteomServer(svr, NewService(*podUID, *shimBinary, *chBinary, *virtiofsdBinary, *kataConfig, *kataNamespace, *kataDebug, interiorNetNS, eth0LinkInfo)) + reflection.Register(svr) + + slog.InfoContext(ctx, "ateom-cloud-hypervisor serving", slog.String("socket", sockPath)) + if err := svr.Serve(lis); err != nil { + return fmt.Errorf("while serving: %w", err) + } + return nil +} + +// ensureSharedPropagation makes path a mount point with rshared propagation +// (self-bind + MS_SHARED|MS_REC), so mounts created beneath it propagate to +// slave binds (kata's mounts/ -> shared/ chain). Idempotent: skips if path is +// already a shared mount point. +func ensureSharedPropagation(ctx context.Context, path string) error { + if err := os.MkdirAll(path, 0o750); err != nil { + return fmt.Errorf("creating %q: %w", path, err) + } + if b, err := os.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(b), "\n") { + // mountinfo: ID parentID major:minor root mountpoint opts optional... - fstype ... + fields := strings.Fields(line) + if len(fields) >= 7 && fields[4] == path && strings.Contains(line, "shared:") { + slog.InfoContext(ctx, "Mount already shared", slog.String("path", path)) + return nil + } + } + } + if err := unix.Mount(path, path, "", unix.MS_BIND, ""); err != nil { + return fmt.Errorf("self-binding %q: %w", path, err) + } + if err := unix.Mount("", path, "", unix.MS_SHARED|unix.MS_REC, ""); err != nil { + return fmt.Errorf("marking %q rshared: %w", path, err) + } + slog.InfoContext(ctx, "Made mount rshared for kata virtio-fs propagation", slog.String("path", path)) + return nil +} + +// startSyslogSink binds a unixgram socket at /dev/log and drains it, so +// virtiofsd's --syslog delivery (which kata always enables) succeeds inside a +// pod that has no syslog daemon. Best-effort. +func startSyslogSink(ctx context.Context) { + const addr = "/dev/log" + _ = os.Remove(addr) + conn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: addr, Net: "unixgram"}) + if err != nil { + slog.WarnContext(ctx, "Failed to create /dev/log syslog sink; virtiofsd --syslog may fail", slog.Any("err", err)) + return + } + slog.InfoContext(ctx, "syslog sink listening", slog.String("addr", addr)) + go func() { + buf := make([]byte, 8192) + for { + if _, _, err := conn.ReadFromUnix(buf); err != nil { + return + } + } + }() +} + +// AteomService is the cloud-hypervisor implementation of ateompb.AteomServer. +type AteomService struct { + ateompb.UnimplementedAteomServer + + // lock serializes RPCs; like ateom-gvisor, the run/checkpoint/restore + // lifecycle is not safe to drive concurrently. + lock sync.Mutex + + podUID string + shimBinary string + chBinary string + virtiofsdBinary string + kataConfig string + namespace string + kataDebug bool + + // interiorNetNS holds the pod's eth0 while a workload runs (mirrors + // ateom-gvisor); kata is pointed at it. eth0LinkInfo is eth0's scraped + // addrs/routes, restored after moving the link into the interior netns. + interiorNetNS netns.NsHandle + eth0LinkInfo *SaveLinkInfo + + // running maps actor id -> the live micro-VM, kept so CheckpointWorkload can + // pause+snapshot+teardown the same sandbox (and RestoreWorkload can track the + // CH it relaunched). + running map[string]*runningActor +} + +var _ ateompb.AteomServer = (*AteomService)(nil) + +// NewService creates a new AteomService. +func NewService(podUID, shimBinary, chBinary, virtiofsdBinary, kataConfig, namespace string, kataDebug bool, interiorNetNS netns.NsHandle, eth0LinkInfo *SaveLinkInfo) *AteomService { + return &AteomService{ + podUID: podUID, + shimBinary: shimBinary, + chBinary: chBinary, + virtiofsdBinary: virtiofsdBinary, + kataConfig: kataConfig, + namespace: namespace, + kataDebug: kataDebug, + interiorNetNS: interiorNetNS, + eth0LinkInfo: eth0LinkInfo, + running: map[string]*runningActor{}, + } +} diff --git a/cmd/ateom-cloud-hypervisor/main_unsupported.go b/cmd/ateom-cloud-hypervisor/main_unsupported.go new file mode 100644 index 000000000..2d8a864c0 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/main_unsupported.go @@ -0,0 +1,27 @@ +//go:build !linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "os" +) + +func main() { + fmt.Fprintln(os.Stderr, "ateom-cloud-hypervisor is only supported on Linux") + os.Exit(1) +} diff --git a/cmd/ateom-cloud-hypervisor/net.go b/cmd/ateom-cloud-hypervisor/net.go new file mode 100644 index 000000000..2d7ca1b97 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/net.go @@ -0,0 +1,365 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +// Networking mirrors cmd/ateom-gvisor: the pod's eth0 is moved into a named +// "interior" network namespace (its addresses/routes restored after the move), +// and the sandbox is pointed at that netns. gVisor reads the link via AF_PACKET; +// kata instead finds eth0 in the netns and builds a tap + TC mirror for the VM's +// virtio-net. (Copied with light adaptation; expected to be de-duplicated into a +// shared package on a later rebase.) + +import ( + "context" + "fmt" + "log/slog" + "net" + "os" + "runtime" + "sort" + + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" + "golang.org/x/sys/unix" +) + +// SaveLinkInfo captures a link's addresses and routes so they can be restored +// after the link is moved into another network namespace (which drops them). +type SaveLinkInfo struct { + Addresses []SaveAddr + Routes []SaveRoute + MTU int +} + +type SaveAddr struct { + Addr net.IPNet + Scope int + Broadcast net.IP +} + +type SaveRoute struct { + Scope uint8 + Dst net.IPNet + Src net.IP + Gateway net.IP + Protocol int + Type int +} + +func scrapeLink(link netlink.Link) (*SaveLinkInfo, error) { + rawAddrs, err := netlink.AddrList(link, netlink.FAMILY_ALL) + if err != nil { + return nil, fmt.Errorf("while scraping addresses: %w", err) + } + var addrs []SaveAddr + for _, rawAddr := range rawAddrs { + addrs = append(addrs, SaveAddr{ + Addr: *rawAddr.IPNet, + Scope: rawAddr.Scope, + Broadcast: rawAddr.Broadcast, + }) + } + + rawRoutes, err := netlink.RouteList(link, netlink.FAMILY_ALL) + if err != nil { + return nil, fmt.Errorf("while scraping routes: %w", err) + } + var routes []SaveRoute + for _, rawRoute := range rawRoutes { + dst := net.IPNet{} + if rawRoute.Dst != nil { + dst = *rawRoute.Dst + } + routes = append(routes, SaveRoute{ + Scope: uint8(rawRoute.Scope), + Dst: dst, + Src: rawRoute.Src, + Gateway: rawRoute.Gw, + Protocol: int(rawRoute.Protocol), + Type: rawRoute.Type, + }) + } + + return &SaveLinkInfo{Addresses: addrs, Routes: routes, MTU: link.Attrs().MTU}, nil +} + +func restoreLink(ctx context.Context, link netlink.Link, info *SaveLinkInfo) error { + for i, saveAddr := range info.Addresses { + addr := &netlink.Addr{ + IPNet: &saveAddr.Addr, + Scope: saveAddr.Scope, + Broadcast: saveAddr.Broadcast, + } + if err := netlink.AddrReplace(link, addr); err != nil { + return fmt.Errorf("while restoring addr %d onto link: %w", i, err) + } + } + // Link-scope routes must be installed before gateway routes so the kernel + // can resolve each gateway's nexthop (fib_check_nh_v4_gw). + routes := append([]SaveRoute(nil), info.Routes...) + sort.SliceStable(routes, func(i, j int) bool { + return routes[i].Gateway == nil && routes[j].Gateway != nil + }) + for i, saveRoute := range routes { + dst := saveRoute.Dst + route := &netlink.Route{ + LinkIndex: link.Attrs().Index, + Scope: netlink.Scope(saveRoute.Scope), + Dst: &dst, + Src: saveRoute.Src, + Gw: saveRoute.Gateway, + Protocol: netlink.RouteProtocol(saveRoute.Protocol), + Type: saveRoute.Type, + } + slog.InfoContext(ctx, "Restoring route", slog.String("dst", dst.String()), slog.Any("gateway", saveRoute.Gateway)) + if err := netlink.RouteReplace(route); err != nil { + return fmt.Errorf("while restoring route %d: %w", i, err) + } + } + return nil +} + +// createNetNSWithoutSwitching creates a named netns and returns its handle, +// restoring the caller's current netns before returning. +func createNetNSWithoutSwitching(name string) (netns.NsHandle, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + curNetNS, err := netns.Get() + if err != nil { + return -1, fmt.Errorf("while getting current netns: %w", err) + } + defer func() { + if err := netns.Set(curNetNS); err != nil { + panic(fmt.Sprintf("Failed to restore original netns: %v", err)) + } + }() + + interiorNetNS, err := netns.NewNamed(name) + if err != nil { + return -1, fmt.Errorf("while creating interior network namespace: %w", err) + } + return interiorNetNS, nil +} + +// netNSDo runs do() with the OS thread switched into targetNS, then restores it. +func netNSDo(ctx context.Context, targetNS netns.NsHandle, do func(context.Context) error) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + curNetNS, err := netns.Get() + if err != nil { + return fmt.Errorf("while getting current netns: %w", err) + } + defer func() { + if err := netns.Set(curNetNS); err != nil { + panic(fmt.Sprintf("Failed to restore original netns: %v", err)) + } + }() + + if err := netns.Set(targetNS); err != nil { + return fmt.Errorf("setting target netns: %w", err) + } + if err := do(ctx); err != nil { + return fmt.Errorf("while executing function in target netns: %w", err) + } + return nil +} + +// moveEth0IntoInteriorNetns moves the pod's eth0 into the interior netns and +// restores its addresses/routes there. After this, the interior netns holds a +// fully configured eth0 the sandbox runtime can consume. +func (s *AteomService) moveEth0IntoInteriorNetns(ctx context.Context) error { + eth0Link, err := netlink.LinkByName("eth0") + if err != nil { + return fmt.Errorf("while getting netlink link for eth0: %w", err) + } + if err := netlink.LinkSetNsFd(eth0Link, int(s.interiorNetNS)); err != nil { + return fmt.Errorf("while moving eth0 into interior network namespace: %w", err) + } + return netNSDo(ctx, s.interiorNetNS, func(ctx context.Context) error { + // The interior netns is persistent (created once at boot). kata's tcfilter + // model creates a tap (and may leave it behind when a failed run is torn + // down by driving the shim directly). A leftover tap with the same name/ + // index makes the next run's qdisc add fail "Failed to add qdisc ... file + // exists". Delete every link that isn't lo or the eth0 we're about to move + // in, so kata always builds its network on a clean slate. + if links, lerr := netlink.LinkList(); lerr == nil { + for _, l := range links { + name := l.Attrs().Name + if name == "lo" || name == "eth0" { + continue + } + if delErr := netlink.LinkDel(l); delErr != nil { + slog.WarnContext(ctx, "Failed to delete leftover link in interior netns", slog.String("link", name), slog.Any("err", delErr)) + } else { + slog.InfoContext(ctx, "Deleted leftover link in interior netns", slog.String("link", name)) + } + } + } + loLink, err := netlink.LinkByName("lo") + if err != nil { + return fmt.Errorf("while acquiring lo in interior netns: %w", err) + } + if err := netlink.LinkSetUp(loLink); err != nil { + return fmt.Errorf("while bringing up lo in interior netns: %w", err) + } + link, err := netlink.LinkByName("eth0") + if err != nil { + return fmt.Errorf("while acquiring eth0 in interior netns: %w", err) + } + if err := netlink.LinkSetUp(link); err != nil { + return fmt.Errorf("while bringing up eth0 in interior netns: %w", err) + } + if err := restoreLink(ctx, link, s.eth0LinkInfo); err != nil { + return fmt.Errorf("while restoring eth0 routes/addresses in interior netns: %w", err) + } + // kata's tcfilter network model adds a clsact qdisc to eth0; a stale one + // (left by a prior failed attempt, carried across the netns move) makes + // kata's add fail "Failed to add qdisc ... file exists". Remove any + // clsact/ingress qdisc so kata can add its own. + if qdiscs, qerr := netlink.QdiscList(link); qerr == nil { + for _, q := range qdiscs { + if t := q.Type(); t == "clsact" || t == "ingress" { + if delErr := netlink.QdiscDel(q); delErr != nil { + slog.WarnContext(ctx, "Failed to delete stale qdisc on eth0", slog.String("type", t), slog.Any("err", delErr)) + } else { + slog.InfoContext(ctx, "Removed stale qdisc on eth0", slog.String("type", t)) + } + } + } + } + return nil + }) +} + +// setupRestoreTap recreates, in the interior netns, the tap + TC-mirror wiring +// kata's tcfilter network model builds at boot: a tap device cross-connected to +// eth0 with mirred-redirect ingress filters in both directions. Returns the +// open tap FDs (one per queue pair) for cloud-hypervisor to adopt via +// vm.restore net_fds (the snapshot's virtio-net device is fd-backed, so CH +// requires fresh FDs on restore). Call after moveEth0IntoInteriorNetns. +func (s *AteomService) setupRestoreTap(ctx context.Context, name string, queuePairs int) ([]*os.File, error) { + var fds []*os.File + err := netNSDo(ctx, s.interiorNetNS, func(ctx context.Context) error { + eth0, err := netlink.LinkByName("eth0") + if err != nil { + return fmt.Errorf("acquiring eth0 in interior netns: %w", err) + } + if old, lerr := netlink.LinkByName(name); lerr == nil { + _ = netlink.LinkDel(old) + } + flags := netlink.TUNTAP_NO_PI | netlink.TUNTAP_VNET_HDR + if queuePairs > 1 { + flags |= netlink.TUNTAP_MULTI_QUEUE + } + tap := &netlink.Tuntap{ + LinkAttrs: netlink.LinkAttrs{Name: name, MTU: eth0.Attrs().MTU}, + Mode: netlink.TUNTAP_MODE_TAP, + Flags: flags, + Queues: queuePairs, + } + if err := netlink.LinkAdd(tap); err != nil { + return fmt.Errorf("creating tap %q: %w", name, err) + } + fds = tap.Fds + if err := netlink.LinkSetUp(tap); err != nil { + return fmt.Errorf("bringing up tap %q: %w", name, err) + } + // Cross-connect: everything arriving on eth0 redirects out the tap and + // vice versa (kata's TCFilterModel: ingress qdisc + match-all u32 with a + // mirred egress-redirect action, here via U32.RedirIndex). + for _, pair := range [][2]netlink.Link{{eth0, tap}, {tap, eth0}} { + qdisc := &netlink.Ingress{QdiscAttrs: netlink.QdiscAttrs{ + LinkIndex: pair[0].Attrs().Index, + Parent: netlink.HANDLE_INGRESS, + Handle: netlink.MakeHandle(0xffff, 0), + }} + if err := netlink.QdiscReplace(qdisc); err != nil { + return fmt.Errorf("adding ingress qdisc to %q: %w", pair[0].Attrs().Name, err) + } + filter := &netlink.U32{ + FilterAttrs: netlink.FilterAttrs{ + LinkIndex: pair[0].Attrs().Index, + Parent: netlink.MakeHandle(0xffff, 0), + Priority: 1, + Protocol: unix.ETH_P_ALL, + }, + ClassId: netlink.MakeHandle(1, 1), + RedirIndex: pair[1].Attrs().Index, + } + if err := netlink.FilterAdd(filter); err != nil { + return fmt.Errorf("adding mirred filter %s -> %s: %w", pair[0].Attrs().Name, pair[1].Attrs().Name, err) + } + } + return nil + }) + if err != nil { + for _, f := range fds { + _ = f.Close() + } + return nil, err + } + return fds, nil +} + +// ensureEth0InPodNetns moves eth0 back to the pod netns if a prior Run/Restore +// left it in the interior netns. Idempotent. +func (s *AteomService) ensureEth0InPodNetns(ctx context.Context) error { + if _, err := netlink.LinkByName("eth0"); err == nil { + return nil + } + podNetNS, err := netns.Get() + if err != nil { + return fmt.Errorf("while getting pod netns: %w", err) + } + var moved bool + err = netNSDo(ctx, s.interiorNetNS, func(_ context.Context) error { + link, lookupErr := netlink.LinkByName("eth0") + if lookupErr != nil { + return nil + } + if mvErr := netlink.LinkSetNsFd(link, int(podNetNS)); mvErr != nil { + return fmt.Errorf("while moving eth0 to pod netns: %w", mvErr) + } + moved = true + return nil + }) + if moved { + slog.WarnContext(ctx, "Recovered eth0 from interior netns to pod netns") + } + return err +} + +// returnEth0ToPodNetns moves eth0 back from the interior netns to the pod netns +// (called after a workload is torn down). +func (s *AteomService) returnEth0ToPodNetns(ctx context.Context) error { + podNetNS, err := netns.Get() + if err != nil { + return fmt.Errorf("while getting pod netns: %w", err) + } + return netNSDo(ctx, s.interiorNetNS, func(_ context.Context) error { + link, lookupErr := netlink.LinkByName("eth0") + if lookupErr != nil { + return nil // already gone + } + if err := netlink.LinkSetNsFd(link, int(podNetNS)); err != nil { + return fmt.Errorf("while sending eth0 back to pod netns: %w", err) + } + return nil + }) +} diff --git a/cmd/ateom-cloud-hypervisor/run.go b/cmd/ateom-cloud-hypervisor/run.go new file mode 100644 index 000000000..9c86a07c4 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/run.go @@ -0,0 +1,793 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/ch" + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata" + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpb" + "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/agent-substrate/substrate/internal/proto/ateompb" + ctrtypes "github.com/containerd/containerd/api/types" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/vishvananda/netlink" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// runningActor holds the live state for one actor's micro-VM. +// +// Two ownership modes: +// - kata-shim-owned (from RunWorkload): shim != nil; kata owns the CH process. +// - ateom-owned (from RestoreWorkload): shim == nil; ateom relaunched CH + +// virtiofsd directly and owns those processes. +type runningActor struct { + containerName string + + // kata-shim-owned + shim *kata.Shim + + // ateom-owned (post-restore) + chCmd *exec.Cmd + vfsdCmd *exec.Cmd + // apiSocket is the CH api-socket for an ateom-owned (restored) VMM; empty + // for kata-shim-owned actors (use kata.CLHSocketPath then). + apiSocket string +} + +// sharedDirTar is the file name (under the checkpoint/restore dir) holding the +// captured virtio-fs shared-dir contents shipped alongside the CH snapshot. +const sharedDirTar = "shared-dir.tar" + +// Asset names in RunWorkloadRequest.runtime_asset_paths (set by atelet's +// fetchRuntimeAssets, keyed by the ActorTemplate runtime asset names). +const ( + assetShim = "kata-shim" + assetCH = "cloud-hypervisor" + assetVirtiofsd = "virtiofsd" + assetKernel = "kata-kernel" + assetImage = "kata-image" + assetConfig = "kata-config" +) + +// resolvedRuntime holds the concrete binary/config paths for a request, taken +// from fetched runtime assets when present, else the process flags. +type resolvedRuntime struct { + shim string + ch string + virtiofsd string + configFile string +} + +func firstNonEmpty(a, b string) string { + if a != "" { + return a + } + return b +} + +// resolveRuntime determines the kata shim / cloud-hypervisor / virtiofsd binaries +// and the kata config for a request. atelet fetches these content-addressed and +// passes their local paths; when a base config + kernel + image are present we +// render a configuration.toml pointing at the fetched paths (fetch-not-bake). +// Falls back to the process flags for anything not supplied. +func (s *AteomService) resolveRuntime(actorDir string, paths map[string]string) (resolvedRuntime, error) { + r := resolvedRuntime{ + shim: firstNonEmpty(paths[assetShim], s.shimBinary), + ch: firstNonEmpty(paths[assetCH], s.chBinary), + virtiofsd: firstNonEmpty(paths[assetVirtiofsd], s.virtiofsdBinary), + configFile: s.kataConfig, + } + base, kernel, image := paths[assetConfig], paths[assetKernel], paths[assetImage] + if base != "" && kernel != "" && image != "" { + baseBytes, err := os.ReadFile(base) + if err != nil { + return r, fmt.Errorf("reading base kata config %q: %w", base, err) + } + rendered, err := kata.RenderConfig(baseBytes, kata.ConfigAssets{ + Kernel: kernel, Image: image, Hypervisor: r.ch, Virtiofsd: r.virtiofsd, + }) + if err != nil { + return r, fmt.Errorf("rendering kata config: %w", err) + } + if s.kataDebug { + // Verbose kata: hypervisor/agent/runtime debug on, guest console (with + // the kata-agent's logs) forwarded into the shim log -> pod logs. + rendered = kata.EnableDebug(rendered) + } + cfgPath := filepath.Join(actorDir, "configuration.toml") + if err := os.MkdirAll(actorDir, 0o700); err != nil { + return r, fmt.Errorf("creating actor dir: %w", err) + } + if err := os.WriteFile(cfgPath, rendered, 0o600); err != nil { + return r, fmt.Errorf("writing rendered kata config: %w", err) + } + r.configFile = cfgPath + } + return r, nil +} + +// RunWorkload boots the actor as a kata + cloud-hypervisor micro-VM. +// +// Contract with atelet (mirrors ateom-gvisor): +// - The runtime assets (kata shim, guest kernel, rootfs, cloud-hypervisor, +// virtiofsd) are on disk and configured. +// - The OCI bundle (config.json + populated rootfs/) is prepared per container. +// +// ateom drives the kata shim v2 ttrpc Task API directly (no containerd daemon): +// Bootstrap (start action) -> Create (boots the CH VM) -> Start. The CH api +// socket then lives at kata.CLHSocketPath(id), which CheckpointWorkload drives. +// +// Proven end-to-end by TestKataLifecycle: boot + run + pause + resume of a +// busybox container in a CH micro-VM, no containerd. +func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkloadRequest) (resp *ateompb.RunWorkloadResponse, retErr error) { + s.lock.Lock() + defer s.lock.Unlock() + + ns := req.GetActorTemplateNamespace() + name := req.GetActorTemplateName() + id := req.GetActorId() + + containers := req.GetSpec().GetContainers() + if len(containers) != 1 { + // POC: one container per sandbox. Multi-container pods are future work. + return nil, status.Errorf(codes.Unimplemented, "ateom-cloud-hypervisor supports exactly one container, got %d", len(containers)) + } + containerName := containers[0].GetName() + + // Networking (mirrors ateom-gvisor): move the pod's eth0 into the interior + // netns (addrs/routes restored) and point kata at it. Roll back on failure so + // the pod isn't left without eth0 for the next actor. + if err := s.ensureEth0InPodNetns(ctx); err != nil { + return nil, fmt.Errorf("while recovering eth0 from prior failure: %w", err) + } + if err := s.moveEth0IntoInteriorNetns(ctx); err != nil { + return nil, fmt.Errorf("while moving eth0 into interior netns: %w", err) + } + defer func() { + if retErr != nil { + if cleanupErr := s.ensureEth0InPodNetns(ctx); cleanupErr != nil { + slog.WarnContext(ctx, "Failed to roll back eth0 after Run failure", slog.Any("err", cleanupErr)) + } + } + }() + + bundle := ateompath.OCIBundlePath(ns, name, id, containerName) + if err := ensureKataCompatibleSpec(bundle, id, ateompath.AteomNetNSPath(s.podUID)); err != nil { + return nil, fmt.Errorf("while preparing kata OCI spec: %w", err) + } + + actorDir := ateompath.ActorPath(ns, name, id) + rr, err := s.resolveRuntime(actorDir, req.GetRuntimeAssetPaths()) + if err != nil { + return nil, fmt.Errorf("while resolving runtime assets: %w", err) + } + shim := &kata.Shim{ + Binary: rr.shim, + ID: id, + Bundle: bundle, + Namespace: s.namespace, + // No containerd: these only need to be stable, unique paths. The shim + // derives its ttrpc socket from (GRPCAddress, Namespace, ID) and would + // publish events to TTRPCAddress (best-effort, logged if unreachable). + GRPCAddress: filepath.Join(actorDir, "shim-containerd.sock"), + TTRPCAddress: filepath.Join(actorDir, "shim-containerd.sock.ttrpc"), + ConfigFile: rr.configFile, + Diagnostics: slogWriter{ctx}, + Debug: s.kataDebug, + } + + // Clear any leftover kata host-side state for this (deterministic) sandbox id + // from a prior failed/torn-down attempt, so the shim's share + virtiofsd + // socket setup doesn't collide ("address already in use" / "directory not + // empty"). Since we drive the shim directly, failed Creates don't self-clean. + kata.CleanupSandboxState(id) + + slog.InfoContext(ctx, "Bootstrapping kata shim", slog.String("id", id), slog.String("bundle", bundle)) + if err := shim.Bootstrap(ctx); err != nil { + return nil, fmt.Errorf("while bootstrapping kata shim: %w", err) + } + // On any failure after Bootstrap, tear the shim down so the pod isn't wedged. + defer func() { + if retErr != nil { + _ = shim.Shutdown(ctx, true) + _ = shim.Close() + _ = shim.CleanupAction(ctx) + } + }() + + // Pass the rootfs as a bind mount, mirroring what containerd's snapshotter + // provides: the mount SOURCE must differ from the target (/rootfs), + // so kata's shim bind-mounts source->target and virtio-fs-shares the target. + // atelet populates /rootfs directly; a self-bind (source==target) is + // not shared, leaving the guest rootfs empty -> agent createContainer ENOENT. + // Relocate the populated rootfs to a sibling source dir (once per bundle) and + // point kata at it, with an empty /rootfs as the mount target. + rootfsDir := filepath.Join(bundle, "rootfs") + rootfsSrc := filepath.Join(bundle, "rootfs-src") + if fi, statErr := os.Stat(rootfsSrc); statErr != nil || !fi.IsDir() { + if err := os.Rename(rootfsDir, rootfsSrc); err != nil { + return nil, fmt.Errorf("relocating populated rootfs to source dir: %w", err) + } + } + if err := os.MkdirAll(rootfsDir, 0o755); err != nil { + return nil, fmt.Errorf("creating rootfs mount target: %w", err) + } + if _, err := shim.Create(ctx, kata.CreateOptions{ + Rootfs: []*ctrtypes.Mount{{ + Type: "bind", + Source: rootfsSrc, + Options: []string{"bind", "rw"}, + }}, + }); err != nil { + return nil, fmt.Errorf("while creating container/VM: %w", err) + } + slog.InfoContext(ctx, "Micro-VM created", slog.String("clh_socket", kata.CLHSocketPath(id))) + + if _, err := shim.Start(ctx); err != nil { + return nil, fmt.Errorf("while starting container: %w", err) + } + + s.running[id] = &runningActor{shim: shim, containerName: containerName} + slog.InfoContext(ctx, "Actor started", slog.String("id", id)) + return &ateompb.RunWorkloadResponse{}, nil +} + +// ensureKataCompatibleSpec augments the bundle's config.json with the fields +// kata's OCI conversion requires but atelet's (gVisor-oriented) spec omits. +// Without linux.resources, kata's ContainerConfig nil-derefs and the shim +// crashes. This shaper is a bridge; a future atelet change should emit +// runtime-appropriate specs so it can retire. +func ensureKataCompatibleSpec(bundle, id, netnsPath string) error { + specPath := filepath.Join(bundle, "config.json") + b, err := os.ReadFile(specPath) + if err != nil { + return fmt.Errorf("reading %q: %w", specPath, err) + } + var spec specs.Spec + if err := json.Unmarshal(b, &spec); err != nil { + return fmt.Errorf("parsing %q: %w", specPath, err) + } + + if spec.Linux == nil { + spec.Linux = &specs.Linux{} + } + if spec.Linux.Resources == nil { + spec.Linux.Resources = defaultKataResources() + } + if spec.Linux.CgroupsPath == "" { + spec.Linux.CgroupsPath = "/ateomchv/" + id + } + + // atelet's spec carries gVisor pause-model CRI annotations + // (container-type=container, sandbox-id=pause). kata reads those and waits + // for a separate "pause" sandbox that we never create, failing with "the + // sandbox hasn't been created". Strip them so kata treats this single + // container as its own sandbox (creates the VM), as in the integration tests. + for k := range spec.Annotations { + if strings.HasPrefix(k, "io.kubernetes.cri.") { + delete(spec.Annotations, k) + } + } + + // Point the network namespace at our interior netns (which holds the pod's + // eth0); kata finds eth0 there and wires it to the VM's virtio-net. + netnsSet := false + for i := range spec.Linux.Namespaces { + if spec.Linux.Namespaces[i].Type == specs.NetworkNamespace { + spec.Linux.Namespaces[i].Path = netnsPath + netnsSet = true + } + } + if !netnsSet { + spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{ + Type: specs.NetworkNamespace, Path: netnsPath, + }) + } + + // Replace atelet's gVisor-oriented mounts (minimal /dev tmpfs, a + // /etc/resolv.conf host bind that ENOENTs against the distroless rootfs) with + // the exact set `ctr run --runtime io.containerd.kata.v2` emits, which kata's + // agent accepts. (POC shaper; pod DNS integration is future work.) + spec.Mounts = defaultKataMounts() + + out, err := json.MarshalIndent(&spec, "", " ") + if err != nil { + return fmt.Errorf("marshaling spec: %w", err) + } + if err := os.WriteFile(specPath, out, 0o600); err != nil { + return fmt.Errorf("writing %q: %w", specPath, err) + } + return nil +} + +// defaultKataMounts mirrors the mount set `ctr run --runtime io.containerd.kata.v2` +// produces (the proven-good shape for the kata agent). +func defaultKataMounts() []specs.Mount { + return []specs.Mount{ + {Destination: "/proc", Type: "proc", Source: "proc", Options: []string{"nosuid", "noexec", "nodev"}}, + {Destination: "/dev", Type: "tmpfs", Source: "tmpfs", Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}}, + {Destination: "/dev/pts", Type: "devpts", Source: "devpts", Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}}, + {Destination: "/dev/shm", Type: "tmpfs", Source: "shm", Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}}, + {Destination: "/dev/mqueue", Type: "mqueue", Source: "mqueue", Options: []string{"nosuid", "noexec", "nodev"}}, + {Destination: "/sys", Type: "sysfs", Source: "sysfs", Options: []string{"nosuid", "noexec", "nodev", "ro"}}, + {Destination: "/run", Type: "tmpfs", Source: "tmpfs", Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}}, + } +} + +// defaultKataResources mirrors the device allowlist + cpu shares that +// `ctr run --runtime io.containerd.kata.v2` emits (the proven-good shape). +func defaultKataResources() *specs.LinuxResources { + dev := func(t string, major, minor int64, access string) specs.LinuxDeviceCgroup { + d := specs.LinuxDeviceCgroup{Allow: true, Type: t, Access: access} + if major != 0 { + d.Major = &major + } + if minor >= 0 { + d.Minor = &minor + } + return d + } + shares := uint64(1024) + return &specs.LinuxResources{ + Devices: []specs.LinuxDeviceCgroup{ + {Allow: false, Access: "rwm"}, + dev("c", 1, 3, "rwm"), // /dev/null + dev("c", 1, 8, "rwm"), // /dev/random + dev("c", 1, 7, "rwm"), // /dev/full + dev("c", 5, 0, "rwm"), // /dev/tty + dev("c", 1, 5, "rwm"), // /dev/zero + dev("c", 1, 9, "rwm"), // /dev/urandom + dev("c", 5, 1, "rwm"), // /dev/console + dev("c", 136, -1, "rwm"), // pts + dev("c", 5, 2, "rwm"), // /dev/ptmx + }, + CPU: &specs.LinuxCPU{Shares: &shares}, + } +} + +// CheckpointWorkload suspends the actor and writes a portable CH snapshot. +// +// Contract with atelet (mirrors ateom-gvisor): after we return, atelet uploads +// the checkpoint dir to object storage, then tears down bundles and resets the +// actor dir. +// +// ateom drives CH's REST api-socket (the one kata created at CLHSocketPath(id)): +// pause -> snapshot file:// (config.json + state.json + +// sparse memory-ranges) -> tear the sandbox down (shim + VMM). Driving CH's +// REST API under a kata-owned VMM does not corrupt shim state. +func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.CheckpointWorkloadRequest) (*ateompb.CheckpointWorkloadResponse, error) { + s.lock.Lock() + defer s.lock.Unlock() + + ns := req.GetActorTemplateNamespace() + name := req.GetActorTemplateName() + id := req.GetActorId() + + // kata-shim-owned actors expose CH at kata's socket; restored (ateom-owned) + // actors at the socket we launched the VMM on. + ra := s.running[id] + chSocket := kata.CLHSocketPath(id) + if ra != nil && ra.apiSocket != "" { + chSocket = ra.apiSocket + } + client := ch.NewClient(chSocket) + if err := client.WaitReady(ctx, 10*time.Second); err != nil { + return nil, fmt.Errorf("while waiting for CH api-socket: %w", err) + } + if err := client.Pause(ctx); err != nil { + return nil, fmt.Errorf("while pausing guest: %w", err) + } + + checkpointDir := ateompath.CheckpointStateDir(ns, name, id) + // Start from a clean dir so CH's snapshot files are the only contents. + if err := os.RemoveAll(checkpointDir); err != nil { + return nil, fmt.Errorf("while clearing checkpoint dir: %w", err) + } + if err := os.MkdirAll(checkpointDir, 0o700); err != nil { + return nil, fmt.Errorf("while creating checkpoint dir: %w", err) + } + + // Capture the virtio-fs shared dir (container rootfs) BEFORE snapshot/ + // teardown — find-paths restore re-opens these by relative path. For a + // kata-shim-owned actor it lives in the shim's mount namespace (nsenter); + // for a restored (ateom-owned) actor we built it ourselves in our own + // namespace (ReconstructSharedDir), so tar it directly. + if ra != nil && ra.shim != nil { + if err := ra.shim.CaptureSharedDir(ctx, filepath.Join(checkpointDir, sharedDirTar)); err != nil { + return nil, fmt.Errorf("while capturing virtio-fs shared dir: %w", err) + } + } else if _, statErr := os.Stat(kata.SharedDir(id)); statErr == nil { + if err := kata.CaptureSharedDirLocal(ctx, id, filepath.Join(checkpointDir, sharedDirTar)); err != nil { + return nil, fmt.Errorf("while capturing local shared dir: %w", err) + } + } else { + slog.WarnContext(ctx, "No shared dir found for actor; skipping shared-dir capture", slog.String("id", id)) + } + + slog.InfoContext(ctx, "Snapshotting guest", slog.String("id", id), slog.String("dir", checkpointDir)) + if err := client.Snapshot(ctx, checkpointDir); err != nil { + return nil, fmt.Errorf("while snapshotting guest: %w", err) + } + + // Report exactly the files we wrote so atelet ships precisely the CH snapshot + // (config.json + state.json + memory-ranges + shared-dir.tar), not gVisor's + // fixed set. + snapshotFiles, err := listFiles(checkpointDir) + if err != nil { + return nil, fmt.Errorf("while listing snapshot files: %w", err) + } + + // Tear down: the actor returns to "available". Best-effort; the snapshot is + // already on disk for atelet to ship. + s.teardownActor(ctx, id, ra, client) + delete(s.running, id) + + // Return eth0 to the pod netns so the next actor can claim it (mirrors gVisor). + if err := s.returnEth0ToPodNetns(ctx); err != nil { + slog.WarnContext(ctx, "Failed to return eth0 to pod netns after checkpoint", slog.Any("err", err)) + } + + slog.InfoContext(ctx, "Actor checkpointed", slog.String("id", id), slog.Any("snapshot_files", snapshotFiles)) + return &ateompb.CheckpointWorkloadResponse{SnapshotFiles: snapshotFiles}, nil +} + +// listFiles returns the (relative) names of regular files directly under dir. +func listFiles(dir string) ([]string, error) { + entries, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + var files []string + for _, e := range entries { + if e.Type().IsRegular() { + files = append(files, e.Name()) + } + } + return files, nil +} + +// teardownActor stops the CH VMM and the kata shim for an actor. Best-effort: +// the VM may be paused (post-snapshot), so shutting the VMM down via the CH API +// is the reliable stop; the shim teardown then cleans up sandbox state. ra may +// be nil (e.g. ateom restarted and lost in-memory state). +func (s *AteomService) teardownActor(ctx context.Context, id string, ra *runningActor, client *ch.Client) { + if client != nil { + shutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + if err := client.Shutdown(shutCtx); err != nil { + slog.WarnContext(ctx, "CH shutdown failed (continuing teardown)", slog.Any("err", err)) + } + cancel() + } + if ra == nil { + return + } + // kata-shim-owned: stop the shim (which owns the CH process) + clean up. + if ra.shim != nil { + shutCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + if err := ra.shim.Shutdown(shutCtx, true); err != nil { + slog.WarnContext(ctx, "shim shutdown failed (continuing teardown)", slog.Any("err", err)) + } + cancel() + _ = ra.shim.Close() + clCtx, cancel2 := context.WithTimeout(ctx, 15*time.Second) + if err := ra.shim.CleanupAction(clCtx); err != nil { + slog.WarnContext(ctx, "shim cleanup failed", slog.Any("err", err)) + } + cancel2() + } + // ateom-owned (post-restore): kill the CH + virtiofsd we launched. + if ra.chCmd != nil && ra.chCmd.Process != nil { + _ = ra.chCmd.Process.Kill() + _, _ = ra.chCmd.Process.Wait() + } + if ra.vfsdCmd != nil && ra.vfsdCmd.Process != nil { + _ = ra.vfsdCmd.Process.Kill() + _, _ = ra.vfsdCmd.Process.Wait() + } +} + +// RestoreWorkload restores the actor on a (possibly different) pod by relaunching +// cloud-hypervisor directly from the downloaded snapshot — bypassing the kata +// shim — and resuming. +// +// Contract with atelet: the snapshot dir (config.json + state.json + +// memory-ranges + shared-dir.tar) has been downloaded to RestoreStateDir. +// +// Steps: +// 1. Reconstruct the virtio-fs shared dir (container rootfs) from shared-dir.tar +// so find-paths can re-open the inodes the snapshot references. +// 2. Start virtiofsd on the vhost-user socket the snapshot expects. +// 3. Relaunch CH with --restore source_url=file:// (ch.Restore), which +// reconnects to virtiofsd, recreates vsock, and reloads guest RAM (paused). +// 4. Resume. +// +// The snapshot's config.json references kernel + guest-OS image at static node +// paths (present on any kata node) and per-sandbox sockets under VMDir(id); this +// POC restores under the same id, so those paths line up. ateom then owns the CH. +func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.RestoreWorkloadRequest) (resp *ateompb.RestoreWorkloadResponse, retErr error) { + s.lock.Lock() + defer s.lock.Unlock() + + ns := req.GetActorTemplateNamespace() + name := req.GetActorTemplateName() + id := req.GetActorId() + + restoreDir := ateompath.RestoreStateDir(ns, name, id) + + // Resolve the cloud-hypervisor + virtiofsd binaries (fetched assets or flags). + rr, err := s.resolveRuntime(ateompath.ActorPath(ns, name, id), req.GetRuntimeAssetPaths()) + if err != nil { + return nil, fmt.Errorf("while resolving runtime assets: %w", err) + } + + // Clear leftover per-sandbox state/processes from prior failed attempts + // (stale virtiofsd holding the vhost-user socket, etc.). + kata.CleanupSandboxState(id) + + // The snapshot was taken under the SOURCE actor's id (e.g. the golden + // actor); its config.json references that id's socket paths. Rewrite them to + // this actor's VMDir so the sockets we create are the ones CH reopens. + if err := rewriteSnapshotSocketPaths(restoreDir, id); err != nil { + return nil, fmt.Errorf("while rewriting snapshot socket paths: %w", err) + } + + // 1. Reconstruct the virtio-fs shared dir from the shipped tar. + tarPath := filepath.Join(restoreDir, sharedDirTar) + if _, err := os.Stat(tarPath); err != nil { + return nil, fmt.Errorf("missing shared-dir tar %q: %w", tarPath, err) + } + if err := kata.ReconstructSharedDir(ctx, tarPath, id); err != nil { + return nil, fmt.Errorf("while reconstructing shared dir: %w", err) + } + + // kata's per-sandbox runtime dir holds the sockets the snapshot references. + if err := os.MkdirAll(kata.VMDir(id), 0o700); err != nil { + return nil, fmt.Errorf("while creating VM dir: %w", err) + } + + // 2. Networking: move the pod's eth0 into the interior netns and rebuild + // kata's tap + TC mirror there. The snapshot's virtio-net device is + // fd-backed, so CH requires fresh tap FDs on restore (net_fds). + if err := s.ensureEth0InPodNetns(ctx); err != nil { + return nil, fmt.Errorf("while recovering eth0 from prior failure: %w", err) + } + if err := s.moveEth0IntoInteriorNetns(ctx); err != nil { + return nil, fmt.Errorf("while moving eth0 into interior netns: %w", err) + } + defer func() { + if retErr != nil { + if cleanupErr := s.ensureEth0InPodNetns(ctx); cleanupErr != nil { + slog.WarnContext(ctx, "Failed to roll back eth0 after Restore failure", slog.Any("err", cleanupErr)) + } + } + }() + netDevs, err := ch.SnapshotNetDevices(restoreDir) + if err != nil { + return nil, fmt.Errorf("while reading snapshot net devices: %w", err) + } + var restoredNets []ch.RestoredNet + var tapFiles []*os.File + defer func() { + // CH dups the FDs it adopts (SCM_RIGHTS), so ours close unconditionally. + for _, f := range tapFiles { + _ = f.Close() + } + }() + for i, nd := range netDevs { + files, terr := s.setupRestoreTap(ctx, fmt.Sprintf("tap%d_kata", i), nd.QueuePairs) + if terr != nil { + return nil, fmt.Errorf("while building restore tap for %s: %w", nd.ID, terr) + } + tapFiles = append(tapFiles, files...) + rn := ch.RestoredNet{ID: nd.ID} + for _, f := range files { + rn.FDs = append(rn.FDs, int(f.Fd())) + } + restoredNets = append(restoredNets, rn) + } + + // 3. Start virtiofsd on the vhost-user socket from config.json. + vfsdCmd, err := kata.StartVirtiofsd(ctx, kata.VirtiofsdOptions{ + Binary: rr.virtiofsd, + SocketPath: kata.VirtiofsdSocketPath(id), + SharedDir: kata.SharedDir(id), + Log: slogWriter{ctx}, + }) + if err != nil { + return nil, fmt.Errorf("while starting virtiofsd: %w", err) + } + defer func() { + if retErr != nil && vfsdCmd.Process != nil { + _ = vfsdCmd.Process.Kill() + } + }() + + // 4. Launch a bare VMM and restore with the tap FDs attached (SCM_RIGHTS). + apiSocket := filepath.Join(kata.VMDir(id), "clh-api-restore.sock") + slog.InfoContext(ctx, "Restoring CH from snapshot", slog.String("id", id), slog.String("dir", restoreDir)) + chCmd, client, err := ch.LaunchVMM(ctx, ch.LaunchVMMOptions{ + Binary: rr.ch, + APISocket: apiSocket, + Stdout: slogWriter{ctx}, + Stderr: slogWriter{ctx}, + }) + if err != nil { + return nil, fmt.Errorf("while launching VMM for restore: %w", err) + } + defer func() { + if retErr != nil && chCmd.Process != nil { + _ = chCmd.Process.Kill() + } + }() + if err := client.RestoreWithNetFDs(ctx, restoreDir, restoredNets); err != nil { + return nil, fmt.Errorf("while restoring VM with net FDs: %w", err) + } + + // 5. Resume (restore comes back paused). + if err := client.Resume(ctx); err != nil { + return nil, fmt.Errorf("while resuming restored guest: %w", err) + } + + // 6. The guest's frozen network config carries the SOURCE pod's IP; rewrite + // it to this pod's eth0 address so ingress (router -> pod IP) reaches the + // guest. POC: drive the kata-agent debug console over vsock; the proper fix + // is the agent's UpdateInterface/UpdateRoutes RPCs. + // Retry: right after vm.resume the guest's vsock can refuse connections for + // a moment until the virtio-vsock device settles. + guestMAC := "" + if len(netDevs) > 0 { + guestMAC = netDevs[0].MAC + } + var netErr error + for attempt := 0; attempt < 10; attempt++ { + if netErr = s.reconfigureGuestNetwork(ctx, kata.VsockSocketPath(id), guestMAC); netErr == nil { + break + } + select { + case <-ctx.Done(): + attempt = 10 + case <-time.After(time.Second): + } + } + if netErr != nil { + slog.WarnContext(ctx, "Failed to update restored guest IP (ingress may not work)", slog.Any("err", netErr)) + } + + s.running[id] = &runningActor{chCmd: chCmd, vfsdCmd: vfsdCmd, apiSocket: apiSocket} + slog.InfoContext(ctx, "Actor restored", slog.String("id", id)) + return &ateompb.RestoreWorkloadResponse{}, nil +} + +// rewriteSnapshotSocketPaths updates a CH snapshot config.json's per-sandbox +// socket paths (virtio-fs vhost-user socket, hybrid-vsock socket) from the +// source actor's VMDir to the restoring actor's VMDir. Disk/kernel paths are +// content-addressed static files and identical on every node. +func rewriteSnapshotSocketPaths(snapshotDir, id string) error { + cfgPath := filepath.Join(snapshotDir, "config.json") + b, err := os.ReadFile(cfgPath) + if err != nil { + return err + } + var cfg map[string]any + if err := json.Unmarshal(b, &cfg); err != nil { + return fmt.Errorf("parsing %q: %w", cfgPath, err) + } + if fsList, ok := cfg["fs"].([]any); ok { + for _, f := range fsList { + if fm, ok := f.(map[string]any); ok { + fm["socket"] = kata.VirtiofsdSocketPath(id) + } + } + } + if vsock, ok := cfg["vsock"].(map[string]any); ok { + vsock["socket"] = kata.VsockSocketPath(id) + } + out, err := json.Marshal(cfg) + if err != nil { + return err + } + return os.WriteFile(cfgPath, out, 0o600) +} + +// reconfigureGuestNetwork sets the restored guest's eth0 to THIS pod's IP +// config via the kata-agent's UpdateInterface/UpdateRoutes ttrpc RPCs over +// hybrid vsock (the same channel the kata shim uses, so it works on a restored +// VM with no shim). The snapshot froze the source pod's IP; on a flat pod +// subnet the routes stay valid, only the address must change. guestMAC +// identifies the device the way kata does (and avoids clearing the MAC). +func (s *AteomService) reconfigureGuestNetwork(ctx context.Context, vsockPath, guestMAC string) error { + var addr *SaveAddr + for i := range s.eth0LinkInfo.Addresses { + if v4 := s.eth0LinkInfo.Addresses[i].Addr.IP.To4(); v4 != nil { + addr = &s.eth0LinkInfo.Addresses[i] + break + } + } + if addr == nil { + return fmt.Errorf("no IPv4 address recorded for pod eth0") + } + var gw net.IP + for _, r := range s.eth0LinkInfo.Routes { + if r.Gateway != nil && r.Dst.IP.Equal(net.IPv4zero) { + gw = r.Gateway + break + } + } + + cctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + agent, err := kata.DialAgent(cctx, vsockPath) + if err != nil { + return err + } + defer agent.Close() + + // The address is installed as /32 deliberately: a connected subnet route + // would make the guest ARP directly for same-subnet pod IPs, which only + // works on CNIs that proxy-ARP on the host veth (kind's ptp does, GKE does + // not — observed SYN-ACKs dying on unanswered ARP). With /32 everything + // routes via the gateway, which the node always answers for. + iface := &agentpb.Interface{ + Device: "eth0", + Name: "eth0", + Mtu: uint64(s.eth0LinkInfo.MTU), + HwAddr: guestMAC, + IPAddresses: []*agentpb.IPAddress{{ + Family: agentpb.IPFamily_v4, + Address: addr.Addr.IP.String(), + Mask: "32", + }}, + } + if _, err := agent.UpdateInterface(cctx, iface); err != nil { + return err + } + if gw != nil { + // Replace the guest routing table: gateway host route + default via it + // (the connected subnet route reappears with the address add). + routes := []*agentpb.Route{ + {Dest: gw.String() + "/32", Device: "eth0", Scope: uint32(netlink.SCOPE_LINK), Family: agentpb.IPFamily_v4}, + {Gateway: gw.String(), Device: "eth0", Family: agentpb.IPFamily_v4}, + } + if _, err := agent.UpdateRoutes(cctx, routes); err != nil { + return err + } + } + slog.InfoContext(ctx, "Restored guest network reconfigured", + slog.String("ip", addr.Addr.IP.String()+"/32"), slog.Any("gw", gw)) + return nil +} + +// slogWriter adapts an io.Writer to slog at info level, for the kata shim's +// start/delete diagnostics. +type slogWriter struct{ ctx context.Context } + +func (w slogWriter) Write(p []byte) (int, error) { + slog.InfoContext(w.ctx, "kata shim", slog.String("out", string(p))) + return len(p), nil +} diff --git a/cmd/ateom-cloud-hypervisor/service_integration_test.go b/cmd/ateom-cloud-hypervisor/service_integration_test.go new file mode 100644 index 000000000..b67115517 --- /dev/null +++ b/cmd/ateom-cloud-hypervisor/service_integration_test.go @@ -0,0 +1,216 @@ +//go:build linux + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/ch" + "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata" + "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/agent-substrate/substrate/internal/proto/ateompb" + "golang.org/x/sys/unix" +) + +// TestServiceE2E drives the REAL AteomService gRPC handlers (RunWorkload, +// CheckpointWorkload, RestoreWorkload) against kata + cloud-hypervisor, with +// an atelet-style OCI bundle and the object-storage round trip +// simulated by copying CheckpointStateDir -> RestoreStateDir. This proves the +// whole ateom-cloud-hypervisor service works end-to-end (not just the helpers): +// path derivation, ensureKataCompatibleSpec (the bundle here is a minimal +// gVisor-style spec with no linux.resources), the running map, shared-dir +// capture/reconstruct, virtiofsd relaunch, CH restore, and teardown. +// +// Gated behind KATA_INTEGRATION=1. Env: KATA_ROOTFS_SRC (required), +// KATA_SHIM / KATA_CH / KATA_VIRTIOFSD (sensible defaults provided). +func TestServiceE2E(t *testing.T) { + if os.Getenv("KATA_INTEGRATION") != "1" { + t.Skip("set KATA_INTEGRATION=1 to run (requires kata + /dev/kvm + root)") + } + rootfsSrc := os.Getenv("KATA_ROOTFS_SRC") + if rootfsSrc == "" { + t.Fatal("KATA_ROOTFS_SRC is required") + } + shim := envOrTest("KATA_SHIM", "/opt/kata/bin/containerd-shim-kata-v2") + chBin := envOrTest("KATA_CH", "/usr/local/bin/cloud-hypervisor") + vfsdBin := envOrTest("KATA_VIRTIOFSD", "/usr/local/bin/virtiofsd-patched") + + ns, name := "default", "e2e" + id := fmt.Sprintf("ateomchv-svc-%d", os.Getpid()) + container := "app" + + // --- atelet-style bundle prep at the ateompath the service expects. --- + bundle := ateompath.OCIBundlePath(ns, name, id, container) + rootfs := filepath.Join(bundle, "rootfs") + if err := os.MkdirAll(rootfs, 0o755); err != nil { + t.Fatal(err) + } + if out, err := exec.Command("cp", "-a", rootfsSrc+"/.", rootfs+"/").CombinedOutput(); err != nil { + t.Fatalf("copying rootfs: %v: %s", err, out) + } + writeMinimalGvisorStyleSpec(t, bundle) // no linux.resources -> exercises ensureKataCompatibleSpec + drainBundleLog(t, bundle) // visibility into shim logs if anything fails + + svc := NewService("testpod", shim, chBin, vfsdBin, "", "default", true, -1, &SaveLinkInfo{}) + ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second) + defer cancel() + + t.Cleanup(func() { + cctx, c := context.WithTimeout(context.Background(), 20*time.Second) + svc.teardownActor(cctx, id, svc.running[id], nil) + c() + _ = os.RemoveAll(ateompath.ActorPath(ns, name, id)) + _ = os.RemoveAll(kata.VMDir(id)) + _ = os.RemoveAll(kata.SharedDir(id)) + }) + + // --- RunWorkload. --- + if _, err := svc.RunWorkload(ctx, &ateompb.RunWorkloadRequest{ + ActorTemplateNamespace: ns, ActorTemplateName: name, ActorId: id, + Spec: &ateompb.WorkloadSpec{Containers: []*ateompb.Container{{Name: container}}}, + }); err != nil { + t.Fatalf("RunWorkload: %v", err) + } + t.Log("RunWorkload OK (micro-VM booted via kata)") + + // --- CheckpointWorkload. --- + if _, err := svc.CheckpointWorkload(ctx, &ateompb.CheckpointWorkloadRequest{ + ActorTemplateNamespace: ns, ActorTemplateName: name, ActorId: id, + Spec: &ateompb.WorkloadSpec{Containers: []*ateompb.Container{{Name: container}}}, + }); err != nil { + t.Fatalf("CheckpointWorkload: %v", err) + } + checkpointDir := ateompath.CheckpointStateDir(ns, name, id) + for _, f := range []string{"config.json", "state.json", "memory-ranges", "shared-dir.tar"} { + if _, err := os.Stat(filepath.Join(checkpointDir, f)); err != nil { + t.Fatalf("checkpoint missing %q: %v", f, err) + } + } + t.Log("CheckpointWorkload OK (snapshot + shared-dir.tar written)") + + // --- Simulate atelet's object-storage round trip. --- + restoreDir := ateompath.RestoreStateDir(ns, name, id) + if err := os.MkdirAll(restoreDir, 0o700); err != nil { + t.Fatal(err) + } + if out, err := exec.Command("cp", "-a", checkpointDir+"/.", restoreDir+"/").CombinedOutput(); err != nil { + t.Fatalf("shipping snapshot: %v: %s", err, out) + } + t.Log("shipped snapshot CheckpointStateDir -> RestoreStateDir") + + // --- RestoreWorkload (fresh CH process, ateom-owned). --- + if _, err := svc.RestoreWorkload(ctx, &ateompb.RestoreWorkloadRequest{ + ActorTemplateNamespace: ns, ActorTemplateName: name, ActorId: id, + Spec: &ateompb.WorkloadSpec{Containers: []*ateompb.Container{{Name: container}}}, + }); err != nil { + t.Fatalf("RestoreWorkload: %v", err) + } + t.Log("RestoreWorkload OK") + + // --- Liveness: the restored guest must be live on its new CH. --- + client := ch.NewClient(filepath.Join(kata.VMDir(id), "clh-api-restore.sock")) + if err := client.WaitReady(ctx, 10*time.Second); err != nil { + t.Fatalf("restored CH not ready: %v", err) + } + if err := client.Pause(ctx); err != nil { + t.Errorf("post-restore Pause (liveness): %v", err) + } + if err := client.Resume(ctx); err != nil { + t.Errorf("post-restore Resume (liveness): %v", err) + } + t.Log("E2E OK: actor ran, checkpointed to 'storage', and restored live on a fresh CH process") +} + +func envOrTest(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + +// writeMinimalGvisorStyleSpec writes a deliberately minimal OCI spec (no +// linux.resources / cgroupsPath) so the test exercises ensureKataCompatibleSpec. +func writeMinimalGvisorStyleSpec(t *testing.T, bundle string) { + t.Helper() + spec := map[string]any{ + "ociVersion": "1.0.2", + "process": map[string]any{ + "user": map[string]any{"uid": 0, "gid": 0}, + "args": []string{"sleep", "3600"}, + "env": []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + "cwd": "/", + "capabilities": map[string]any{ + "bounding": []string{"CAP_KILL", "CAP_AUDIT_WRITE", "CAP_NET_BIND_SERVICE"}, + "effective": []string{"CAP_KILL", "CAP_AUDIT_WRITE", "CAP_NET_BIND_SERVICE"}, + "permitted": []string{"CAP_KILL", "CAP_AUDIT_WRITE", "CAP_NET_BIND_SERVICE"}, + }, + }, + "root": map[string]any{"path": "rootfs", "readonly": false}, + "hostname": "ateomchv", + "mounts": []map[string]any{ + {"destination": "/proc", "type": "proc", "source": "proc"}, + {"destination": "/dev", "type": "tmpfs", "source": "tmpfs"}, + {"destination": "/sys", "type": "sysfs", "source": "sysfs", "options": []string{"nosuid", "noexec", "nodev", "ro"}}, + }, + "linux": map[string]any{ + "namespaces": []map[string]any{ + {"type": "pid"}, {"type": "network"}, {"type": "ipc"}, {"type": "uts"}, {"type": "mount"}, + }, + }, + } + b, err := json.MarshalIndent(spec, "", " ") + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(bundle, "config.json"), b, 0o600); err != nil { + t.Fatal(err) + } +} + +// drainBundleLog creates the kata shim's "log" fifo and drains it to t.Log. +func drainBundleLog(t *testing.T, bundle string) { + t.Helper() + logFifo := filepath.Join(bundle, "log") + _ = os.Remove(logFifo) + if err := unix.Mkfifo(logFifo, 0o700); err != nil { + t.Fatalf("mkfifo log: %v", err) + } + go func() { + f, err := os.OpenFile(logFifo, os.O_RDONLY, 0) + if err != nil { + return + } + defer f.Close() + buf := make([]byte, 4096) + for { + n, err := f.Read(buf) + if n > 0 { + t.Logf("[shim-log] %s", buf[:n]) + } + if err != nil { + return + } + } + }() +} From 9984eebfa1d60ecfb78e63486ba875eb4d0695bc Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:53 -0700 Subject: [PATCH 08/14] hack: kind /dev/kvm mount + micro-VM runtime asset scripts - create-kind-cluster.sh mounts /dev/kvm into the node (TEMP: always; make conditional on host support later) and labels it ate.dev/runtime=microvm. - hack/microvm-assets: assemble.sh builds the per-arch asset set (kata static release pieces, cloud-hypervisor v52, virtiofsd built from source with the vhost 0.16 bump that fixes CH snapshot/restore) and prints the sha256s; stage-to-rustfs.sh uploads to the in-cluster bucket for kind. --- hack/create-kind-cluster.sh | 16 ++++ hack/microvm-assets/README.md | 43 +++++++++++ hack/microvm-assets/assemble.sh | 100 +++++++++++++++++++++++++ hack/microvm-assets/stage-to-rustfs.sh | 48 ++++++++++++ 4 files changed, 207 insertions(+) create mode 100644 hack/microvm-assets/README.md create mode 100755 hack/microvm-assets/assemble.sh create mode 100755 hack/microvm-assets/stage-to-rustfs.sh diff --git a/hack/create-kind-cluster.sh b/hack/create-kind-cluster.sh index 2915a95a2..6d1325649 100755 --- a/hack/create-kind-cluster.sh +++ b/hack/create-kind-cluster.sh @@ -49,6 +49,13 @@ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane + # TEMPORARY (micro-VM demo): always bind-mount /dev/kvm into the node so kata + + # cloud-hypervisor worker pods can use KVM. NOTE: this makes cluster creation + # fail if the host lacks /dev/kvm. TODO: probe for /dev/kvm and only emit this + # mount when present (so gVisor-only environments still work). + extraMounts: + - hostPath: /dev/kvm + containerPath: /dev/kvm # cmd/podcertcontroller depends on ClusterTrustBundle & PodCertificateRequest. # They are not enabled by default as of Kubernetes v1.36 # https://github.com/kubernetes/kubernetes/blob/master/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -72,6 +79,15 @@ for node in $("${ROOT}"/hack/kind.sh get nodes --name "${KIND_CLUSTER_NAME}"); d docker exec "${node}" sysctl net.ipv4.conf.all.proxy_arp=1 done +# 2.6 TEMPORARY (micro-VM demo): make /dev/kvm usable inside the node and label +# nodes so micro-VM WorkerPools (nodeSelector ate.dev/runtime=microvm) schedule. +# TODO: gate this on /dev/kvm being present so gVisor-only clusters still work. +echo "Preparing kind nodes for micro-VM (kata + cloud-hypervisor) runtime..." +for node in $("${ROOT}"/hack/kind.sh get nodes --name "${KIND_CLUSTER_NAME}"); do + docker exec "${node}" chmod 666 /dev/kvm + kubectl label node "${node}" ate.dev/runtime=microvm --overwrite +done + # 3. Add the registry config to the nodes echo "Adding registry config to kind nodes..." REGISTRY_DIR="/etc/containerd/certs.d/localhost:${reg_port}" diff --git a/hack/microvm-assets/README.md b/hack/microvm-assets/README.md new file mode 100644 index 000000000..b18fa216f --- /dev/null +++ b/hack/microvm-assets/README.md @@ -0,0 +1,43 @@ +# Micro-VM runtime assets + counter demo (kind, fetch-not-bake) + +The `microvm` runtime (`cmd/ateom-cloud-hypervisor`, kata + cloud-hypervisor) fetches its +toolchain at runtime — nothing kata-specific is baked into the worker image. These helpers +assemble the asset set for your node arch, stage it into the cluster's rustfs S3 bucket, and the +demo manifest points the ActorTemplate at it. + +The worker image base is `debian:stable-slim` (provides `nsenter` + `tar` + glibc for the fetched +Rust binaries); see `.ko.yaml`. When `/dev/kvm` is available, `hack/create-kind-cluster.sh` +mounts it into the node and labels the node `ate.dev/runtime=microvm`. + +## Steps (run on a KVM-capable Linux host matching the node arch) + +1. **Assemble assets for your arch** (builds the patched virtiofsd — needs rust via rustup, NOT + distro cargo which is too old for the v4 lockfile, plus build deps: + `apt-get install -y git libcap-ng-dev libseccomp-dev pkg-config && curl -fsSL https://sh.rustup.rs | sh -s -- -y`): + ```sh + ARCH=arm64 hack/microvm-assets/assemble.sh + ``` + Copy the printed sha256 sums into `demos/counter/counter-microvm.yaml.tmpl` `runtime.assets` + (the committed values are arm64; other arches differ). + +2. **Bring up the cluster + control plane:** + ```sh + hack/create-kind-cluster.sh # mounts /dev/kvm, labels node ate.dev/runtime=microvm + hack/install-ate-kind.sh # control plane + rustfs (bucket: ate-snapshots) + ``` + +3. **Stage assets into rustfs:** + ```sh + OUT="$PWD/microvm-assets-arm64" hack/microvm-assets/stage-to-rustfs.sh + ``` + +4. **Apply the demo + drive it:** + ```sh + BUCKET_NAME=ate-snapshots envsubst < demos/counter/counter-microvm.yaml.tmpl | kubectl apply -f - + ``` + Create an actor from `counter-microvm`, hit the in-RAM counter to increment it, suspend + (checkpoint), resume on a different worker pod, and confirm the count continues — proving the + guest-memory snapshot round-tripped across pods. + +## Notes +- `assets` is single-arch (unlike runsc's amd64/arm64): stage assets matching the node arch. diff --git a/hack/microvm-assets/assemble.sh b/hack/microvm-assets/assemble.sh new file mode 100755 index 000000000..a136b4f8b --- /dev/null +++ b/hack/microvm-assets/assemble.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Assemble the micro-VM (kata + cloud-hypervisor) runtime asset set that +# ateom-cloud-hypervisor fetches at runtime (fetch-not-bake). Run this on a Linux +# host of the TARGET arch. +# +# Produces, under $OUT, the six assets named as the ActorTemplate expects, plus +# their sha256 sums (paste into demos/counter/counter-microvm.yaml.tmpl): +# containerd-shim-kata-v2 cloud-hypervisor virtiofsd-patched +# vmlinux rootfs.img configuration-clh.toml +# +# virtiofsd is BUILT from source with the vhost-0.16 bump (stock virtiofsd breaks +# CH snapshot/restore; the bumped vhost crates carry the REPLY_ACK fix). Build deps on +# Debian/Ubuntu: apt-get install -y git libcap-ng-dev libseccomp-dev pkg-config +# Rust: use rustup (current stable). Distro cargo (e.g. apt's 1.75) is too old — +# virtiofsd's Cargo.lock is lockfile v4, which needs cargo >= 1.78: +# curl -fsSL https://sh.rustup.rs | sh -s -- -y && . "$HOME/.cargo/env" +# +# Env: ARCH (arm64|amd64, default arm64), KATA_VER (3.31.0), CH_VER (v52.0), +# VIRTIOFSD_REF (upstream commit; default b6d2aaa), OUT (default ./microvm-assets-$ARCH). + +set -o errexit -o nounset -o pipefail + +ARCH="${ARCH:-arm64}" +KATA_VER="${KATA_VER:-3.31.0}" +CH_VER="${CH_VER:-v52.0}" +VIRTIOFSD_REF="${VIRTIOFSD_REF:-b6d2aaa}" +OUT="${OUT:-$PWD/microvm-assets-$ARCH}" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +case "$ARCH" in + arm64) CH_ASSET="cloud-hypervisor-static-aarch64" ;; + amd64) CH_ASSET="cloud-hypervisor-static" ;; + *) echo "unsupported ARCH=$ARCH" >&2; exit 1 ;; +esac + +mkdir -p "$OUT" +cd "$WORK" + +echo ">> Downloading kata-static ${KATA_VER} (${ARCH})..." +curl -fSL -o kata-static.tar.zst \ + "https://github.com/kata-containers/kata-containers/releases/download/${KATA_VER}/kata-static-${KATA_VER}-${ARCH}.tar.zst" +mkdir -p kata +tar --zstd -xf kata-static.tar.zst -C kata +KROOT="kata/opt/kata" + +cp "${KROOT}/bin/containerd-shim-kata-v2" "${OUT}/containerd-shim-kata-v2" +cp "$(readlink -f "${KROOT}/share/kata-containers/vmlinux.container")" "${OUT}/vmlinux" +cp "$(readlink -f "${KROOT}/share/kata-containers/kata-containers.img")" "${OUT}/rootfs.img" +cp "${KROOT}/share/defaults/kata-containers/configuration-clh.toml" "${OUT}/configuration-clh.toml" + +echo ">> Downloading cloud-hypervisor ${CH_VER} (${CH_ASSET})..." +curl -fSL -o "${OUT}/cloud-hypervisor" \ + "https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/${CH_VER}/${CH_ASSET}" +chmod +x "${OUT}/cloud-hypervisor" + +echo ">> Building patched virtiofsd (vhost 0.16) from ${VIRTIOFSD_REF}..." +if ! command -v cargo >/dev/null 2>&1; then + echo "cargo not found; install rust + build deps (see header)" >&2 + exit 1 +fi +git clone https://gitlab.com/virtio-fs/virtiofsd.git +( + cd virtiofsd + git checkout "${VIRTIOFSD_REF}" + # Bump the vhost crates to the versions that contain rust-vmm/vhost@14db3cd + # (the snapshot/restore REPLY_ACK fix). Robust to the exact pinned versions. + sed -i -E 's/^vhost-user-backend = "[^"]*"/vhost-user-backend = "0.22"/' Cargo.toml + sed -i -E 's/^vhost = "[^"]*"/vhost = "0.16"/' Cargo.toml + grep -E '^(vhost|vhost-user-backend) =' Cargo.toml + cargo build --release +) +cp "virtiofsd/target/release/virtiofsd" "${OUT}/virtiofsd-patched" +chmod +x "${OUT}/virtiofsd-patched" + +echo +echo ">> Assets assembled in ${OUT}:" +cd "${OUT}" +for f in containerd-shim-kata-v2 cloud-hypervisor virtiofsd-patched vmlinux rootfs.img configuration-clh.toml; do + [ -f "$f" ] || { echo "MISSING: $f" >&2; exit 1; } +done +"${OUT}/virtiofsd-patched" --version 2>/dev/null | head -1 || true +echo +echo ">> sha256 (paste into demos/counter/counter-microvm.yaml.tmpl runtime.assets):" +sha256sum containerd-shim-kata-v2 cloud-hypervisor virtiofsd-patched vmlinux rootfs.img configuration-clh.toml diff --git a/hack/microvm-assets/stage-to-rustfs.sh b/hack/microvm-assets/stage-to-rustfs.sh new file mode 100755 index 000000000..0d4d47087 --- /dev/null +++ b/hack/microvm-assets/stage-to-rustfs.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Stage the assembled micro-VM asset set into the kind cluster's rustfs S3 bucket +# under kata-assets/, where atelet fetches it (per demos/counter/counter-microvm.yaml.tmpl). +# Run after the cluster is up (hack/install-ate-kind.sh) and assemble.sh has produced $OUT. +# +# Requires the `aws` CLI. Env: OUT (asset dir, default ./microvm-assets-arm64), +# BUCKET (default ate-snapshots), NAMESPACE (rustfs namespace, default ate-system). + +set -o errexit -o nounset -o pipefail + +OUT="${OUT:-$PWD/microvm-assets-arm64}" +BUCKET="${BUCKET:-ate-snapshots}" +NAMESPACE="${NAMESPACE:-ate-system}" + +export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-rustfsadmin}" +export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-rustfsadmin}" +export AWS_REGION="${AWS_REGION:-us-east-1}" + +echo ">> Port-forwarding svc/rustfs 9000 in namespace ${NAMESPACE}..." +kubectl -n "${NAMESPACE}" port-forward svc/rustfs 9000:9000 >/tmp/rustfs-pf.log 2>&1 & +PF_PID=$! +trap 'kill "$PF_PID" 2>/dev/null || true' EXIT +sleep 3 + +ENDPOINT="http://localhost:9000" +echo ">> Uploading assets to s3://${BUCKET}/kata-assets/ via ${ENDPOINT}..." +for f in containerd-shim-kata-v2 cloud-hypervisor virtiofsd-patched vmlinux rootfs.img configuration-clh.toml; do + echo " $f" + aws --endpoint-url "${ENDPOINT}" s3 cp "${OUT}/${f}" "s3://${BUCKET}/kata-assets/${f}" +done + +echo ">> Done. Verify:" +aws --endpoint-url "${ENDPOINT}" s3 ls "s3://${BUCKET}/kata-assets/" From a33b031f199a803d052529b2208bd98be48e98e0 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 11:09:53 -0700 Subject: [PATCH 09/14] demos: micro-VM counter template WorkerPool (runtime=microvm) + ActorTemplate with the content-addressed kata/ cloud-hypervisor asset set; the in-RAM counter proves guest memory survives suspend/resume. --- demos/counter/counter-microvm.yaml.tmpl | 90 +++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 demos/counter/counter-microvm.yaml.tmpl diff --git a/demos/counter/counter-microvm.yaml.tmpl b/demos/counter/counter-microvm.yaml.tmpl new file mode 100644 index 000000000..7c46d6df7 --- /dev/null +++ b/demos/counter/counter-microvm.yaml.tmpl @@ -0,0 +1,90 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Micro-VM (kata + cloud-hypervisor) variant of the counter demo. The counter is +# in-RAM (atomic uint64), so a successful suspend/resume across pods shows the +# count continuing — proving the guest memory snapshot round-tripped. +# +# Runtime assets (kata shim, cloud-hypervisor, patched virtiofsd, guest kernel, +# guest rootfs, base configuration.toml) are FETCHED at runtime from the cluster +# object store (rustfs bucket ${BUCKET_NAME}, prefix kata-assets/) — NOT baked +# into the worker image. The sha256 values below are the arm64 asset set +# produced by hack/microvm-assets/assemble.sh; for another architecture, +# assemble that arch's assets and replace the hashes. authentication.gcp routes the fetch to atelet's main +# (rustfs/S3) client rather than the anonymous public-GCS client. + +apiVersion: v1 +kind: Namespace +metadata: + name: ate-demo-counter-microvm + +--- + +apiVersion: ate.dev/v1alpha1 +kind: WorkerPool +metadata: + name: counter-microvm + namespace: ate-demo-counter-microvm +spec: + replicas: 2 + runtime: microvm + ateomImage: ko://github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor + +--- + +apiVersion: ate.dev/v1alpha1 +kind: ActorTemplate +metadata: + name: counter-microvm + namespace: ate-demo-counter-microvm +spec: + # Runsc is unused for the micro-VM runtime but the field is currently required; + # leave a valid reference so validation passes. + runsc: + amd64: + url: "gs://gvisor/releases/nightly/2026-05-19/x86_64/runsc" + sha256Hash: "a397be1abc2420d26bce6c70e6e2ff96c73aaaab929756c56f5e2089ea842b63" + runtime: + type: microvm + authentication: + gcp: {} + assets: + kata-shim: + url: "gs://${BUCKET_NAME}/kata-assets/containerd-shim-kata-v2" + sha256Hash: "3ec6dc6aa1aa326d5bdfac9d6c9dae8d34642f4ef3adddfe06c27564e640c36a" + cloud-hypervisor: + url: "gs://${BUCKET_NAME}/kata-assets/cloud-hypervisor" + sha256Hash: "bf004ddc1a148f47caa87ac49a783b8dbd6bf9bc27abe522ed197df7b982d3b1" + virtiofsd: + url: "gs://${BUCKET_NAME}/kata-assets/virtiofsd-patched" + sha256Hash: "60447339d8070c7de2dc2bea6bdd483b1bdb7a7b7ddff04b5d80bad39578b1c2" + kata-kernel: + url: "gs://${BUCKET_NAME}/kata-assets/vmlinux" + sha256Hash: "a44d663f4ddad20a35527a3578fadef9beb23c1e5cb720e85d6928d6de70d3a1" + kata-image: + url: "gs://${BUCKET_NAME}/kata-assets/rootfs.img" + sha256Hash: "7ebd652760c881374c0a761d34addcb76d9a650e35c10c01b780ebcdd9a1f2aa" + kata-config: + url: "gs://${BUCKET_NAME}/kata-assets/configuration-clh.toml" + sha256Hash: "df504d9be0ed01765fdc8a9467955e1e671eb97724443f65a524bf914ccb818b" + pauseImage: "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4" + containers: + - name: counter + image: ko://github.com/agent-substrate/substrate/demos/counter + command: ["/ko-app/counter"] + workerPoolRef: + namespace: ate-demo-counter-microvm + name: counter-microvm + snapshotsConfig: + location: gs://${BUCKET_NAME}/ate-demo-counter-microvm/ From 6a7c5e37a9d42c3ac7e95e7e97b32ac8ca893f94 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 12:05:30 -0700 Subject: [PATCH 10/14] ateom-cloud-hypervisor: log checkpoint phase durations Attach pause/capture/snapshot/teardown durations to the 'Actor checkpointed' log line (and time the CH API shutdown in teardown). Measuring on GKE showed the CH snapshot write dominates suspend latency and scales with guest memory size: with kata's stock default_memory=2048 the snapshot phase was ~6.8s on a pd-balanced boot disk; a 512MiB guest (set purely via the fetched configuration.toml asset) drops it to ~0.1-0.3s, taking end-to-end suspend from ~18s to ~3.4s and resume from ~4.4s to ~1.6s. --- cmd/ateom-cloud-hypervisor/run.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cmd/ateom-cloud-hypervisor/run.go b/cmd/ateom-cloud-hypervisor/run.go index 9c86a07c4..a56bc4c5d 100644 --- a/cmd/ateom-cloud-hypervisor/run.go +++ b/cmd/ateom-cloud-hypervisor/run.go @@ -400,9 +400,11 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec if err := client.WaitReady(ctx, 10*time.Second); err != nil { return nil, fmt.Errorf("while waiting for CH api-socket: %w", err) } + tPause := time.Now() if err := client.Pause(ctx); err != nil { return nil, fmt.Errorf("while pausing guest: %w", err) } + dPause := time.Since(tPause) checkpointDir := ateompath.CheckpointStateDir(ns, name, id) // Start from a clean dir so CH's snapshot files are the only contents. @@ -418,6 +420,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // kata-shim-owned actor it lives in the shim's mount namespace (nsenter); // for a restored (ateom-owned) actor we built it ourselves in our own // namespace (ReconstructSharedDir), so tar it directly. + tCapture := time.Now() if ra != nil && ra.shim != nil { if err := ra.shim.CaptureSharedDir(ctx, filepath.Join(checkpointDir, sharedDirTar)); err != nil { return nil, fmt.Errorf("while capturing virtio-fs shared dir: %w", err) @@ -429,11 +432,14 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec } else { slog.WarnContext(ctx, "No shared dir found for actor; skipping shared-dir capture", slog.String("id", id)) } + dCapture := time.Since(tCapture) slog.InfoContext(ctx, "Snapshotting guest", slog.String("id", id), slog.String("dir", checkpointDir)) + tSnapshot := time.Now() if err := client.Snapshot(ctx, checkpointDir); err != nil { return nil, fmt.Errorf("while snapshotting guest: %w", err) } + dSnapshot := time.Since(tSnapshot) // Report exactly the files we wrote so atelet ships precisely the CH snapshot // (config.json + state.json + memory-ranges + shared-dir.tar), not gVisor's @@ -445,7 +451,9 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // Tear down: the actor returns to "available". Best-effort; the snapshot is // already on disk for atelet to ship. + tTeardown := time.Now() s.teardownActor(ctx, id, ra, client) + dTeardown := time.Since(tTeardown) delete(s.running, id) // Return eth0 to the pod netns so the next actor can claim it (mirrors gVisor). @@ -453,7 +461,9 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec slog.WarnContext(ctx, "Failed to return eth0 to pod netns after checkpoint", slog.Any("err", err)) } - slog.InfoContext(ctx, "Actor checkpointed", slog.String("id", id), slog.Any("snapshot_files", snapshotFiles)) + slog.InfoContext(ctx, "Actor checkpointed", slog.String("id", id), slog.Any("snapshot_files", snapshotFiles), + slog.Duration("pause", dPause), slog.Duration("capture", dCapture), + slog.Duration("snapshot", dSnapshot), slog.Duration("teardown", dTeardown)) return &ateompb.CheckpointWorkloadResponse{SnapshotFiles: snapshotFiles}, nil } @@ -478,11 +488,13 @@ func listFiles(dir string) ([]string, error) { // be nil (e.g. ateom restarted and lost in-memory state). func (s *AteomService) teardownActor(ctx context.Context, id string, ra *runningActor, client *ch.Client) { if client != nil { + tShutdown := time.Now() shutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) if err := client.Shutdown(shutCtx); err != nil { slog.WarnContext(ctx, "CH shutdown failed (continuing teardown)", slog.Any("err", err)) } cancel() + slog.InfoContext(ctx, "CH API shutdown done", slog.Duration("took", time.Since(tShutdown))) } if ra == nil { return From 6845d45bf1e955c46755c547c2662d43da285bd7 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 12:27:44 -0700 Subject: [PATCH 11/14] ateom-cloud-hypervisor: TODO for first-class guest memory sizing Guest memory currently comes from the fetched configuration.toml asset's default_memory; it should become an ActorTemplate field rewritten during config rendering, since suspend/resume latency scales directly with it. --- cmd/ateom-cloud-hypervisor/run.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/ateom-cloud-hypervisor/run.go b/cmd/ateom-cloud-hypervisor/run.go index a56bc4c5d..9dfbeebdb 100644 --- a/cmd/ateom-cloud-hypervisor/run.go +++ b/cmd/ateom-cloud-hypervisor/run.go @@ -109,6 +109,12 @@ func (s *AteomService) resolveRuntime(actorDir string, paths map[string]string) if err != nil { return r, fmt.Errorf("reading base kata config %q: %w", base, err) } + // TODO: make guest VM memory size a first-class ActorTemplate field and + // rewrite default_memory here (like the asset paths) instead of requiring + // per-size configuration.toml asset variants. Suspend/resume latency + // scales directly with guest memory (CH's snapshot scans/writes the whole + // memfd): on GKE pd-balanced disks, a 512MiB guest measured ~3.4s suspend + // / ~1.6s resume vs ~18s / ~4.4s at kata's stock default_memory=2048. rendered, err := kata.RenderConfig(baseBytes, kata.ConfigAssets{ Kernel: kernel, Image: image, Hypervisor: r.ch, Virtiofsd: r.virtiofsd, }) From cf79a25438b05dbb2e73ea6f5152cd51d469fd7d Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 12:27:44 -0700 Subject: [PATCH 12/14] hack: enable kind micro-VM support only when /dev/kvm exists Probe the Docker environment (the provider VM on macOS, the host on Linux) with a --device run; only then emit the node /dev/kvm extraMount, chmod it, and label nodes ate.dev/runtime=microvm. Clusters without KVM now create cleanly with gVisor-only support instead of failing. --- hack/create-kind-cluster.sh | 43 ++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/hack/create-kind-cluster.sh b/hack/create-kind-cluster.sh index 6d1325649..bce81d464 100755 --- a/hack/create-kind-cluster.sh +++ b/hack/create-kind-cluster.sh @@ -42,20 +42,38 @@ if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true registry:3 fi -# 2. Create kind configuration with containerdConfigPatches and feature gates +# 2. Create kind configuration with containerdConfigPatches and feature gates. +# +# Probe for /dev/kvm where the kind nodes will actually run — inside the Docker +# provider VM on macOS (Lima/Colima/Docker Desktop), the host itself on Linux — +# and only wire up micro-VM (kata + cloud-hypervisor) support when present. +# Without KVM the cluster still works for gVisor. +echo "Probing for /dev/kvm in the Docker environment..." +HAS_KVM=0 +if docker run --rm --device /dev/kvm busybox true >/dev/null 2>&1; then + HAS_KVM=1 + echo "/dev/kvm found: micro-VM (kata + cloud-hypervisor) support will be enabled." +else + echo "/dev/kvm not available: micro-VM support disabled (gVisor still works)." +fi + echo "Creating kind configuration for cluster '${KIND_CLUSTER_NAME}'..." cat < "${ROOT}/bin/kind-config.yaml" kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - # TEMPORARY (micro-VM demo): always bind-mount /dev/kvm into the node so kata + - # cloud-hypervisor worker pods can use KVM. NOTE: this makes cluster creation - # fail if the host lacks /dev/kvm. TODO: probe for /dev/kvm and only emit this - # mount when present (so gVisor-only environments still work). +EOF +if [ "${HAS_KVM}" = "1" ]; then + cat <> "${ROOT}/bin/kind-config.yaml" + # Bind-mount /dev/kvm into the node so micro-VM (kata + cloud-hypervisor) + # worker pods can use KVM. extraMounts: - hostPath: /dev/kvm containerPath: /dev/kvm +EOF +fi +cat <> "${ROOT}/bin/kind-config.yaml" # cmd/podcertcontroller depends on ClusterTrustBundle & PodCertificateRequest. # They are not enabled by default as of Kubernetes v1.36 # https://github.com/kubernetes/kubernetes/blob/master/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -79,14 +97,15 @@ for node in $("${ROOT}"/hack/kind.sh get nodes --name "${KIND_CLUSTER_NAME}"); d docker exec "${node}" sysctl net.ipv4.conf.all.proxy_arp=1 done -# 2.6 TEMPORARY (micro-VM demo): make /dev/kvm usable inside the node and label +# 2.6 When KVM is available: make /dev/kvm usable inside the node and label # nodes so micro-VM WorkerPools (nodeSelector ate.dev/runtime=microvm) schedule. -# TODO: gate this on /dev/kvm being present so gVisor-only clusters still work. -echo "Preparing kind nodes for micro-VM (kata + cloud-hypervisor) runtime..." -for node in $("${ROOT}"/hack/kind.sh get nodes --name "${KIND_CLUSTER_NAME}"); do - docker exec "${node}" chmod 666 /dev/kvm - kubectl label node "${node}" ate.dev/runtime=microvm --overwrite -done +if [ "${HAS_KVM}" = "1" ]; then + echo "Preparing kind nodes for micro-VM (kata + cloud-hypervisor) runtime..." + for node in $("${ROOT}"/hack/kind.sh get nodes --name "${KIND_CLUSTER_NAME}"); do + docker exec "${node}" chmod 666 /dev/kvm + kubectl label node "${node}" ate.dev/runtime=microvm --overwrite + done +fi # 3. Add the registry config to the nodes echo "Adding registry config to kind nodes..." From 5eb3d0d15bf42f421cffed493b513b767bbcd533 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 12:47:26 -0700 Subject: [PATCH 13/14] controller: document micro-VM node-selection convention + TODO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GKE attaches no label or taint to nested-virt node pools (verified on a live pool — unlike GKE Sandbox's automatic sandbox.gke.io/runtime=gvisor), so there is no upstream label to mirror: ate.dev/runtime=microvm stays our cross-platform convention until WorkerPool node selection is configurable. The toleration covers operator-tainted dedicated KVM pools. --- internal/controllers/workerpool_controller.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/controllers/workerpool_controller.go b/internal/controllers/workerpool_controller.go index 7e5a19dd2..18e5c3be6 100644 --- a/internal/controllers/workerpool_controller.go +++ b/internal/controllers/workerpool_controller.go @@ -167,6 +167,16 @@ func buildDeploymentApplyConfig(wp *atev1alpha1.WorkerPool) *appsv1ac.Deployment // Micro-VM pods must land on KVM-capable, nested-virt nodes: add the // /dev/kvm host device and pin placement via nodeSelector + toleration on // ate.dev/runtime=microvm. + // + // TODO: make node selection (and tolerations) configurable on the + // WorkerPool spec instead of hardcoding this convention. GKE attaches NO + // label or taint to nested-virt node pools (verified empirically — unlike + // GKE Sandbox's automatic sandbox.gke.io/runtime=gvisor label + taint), so + // there is no upstream label to select on: ate.dev/runtime=microvm is our + // own convention, applied by hack/create-kind-cluster.sh on kind and via + // --node-labels at GKE node pool creation. The toleration is likewise our + // convention so operators MAY taint dedicated KVM pools to reserve them + // for micro-VM workers; untainted pools schedule fine too. if microVM { podSpec. WithVolumes(corev1ac.Volume(). From d0053635c2978faa4afd5fbcea9d3851ffa8c02d Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Fri, 12 Jun 2026 13:12:50 -0700 Subject: [PATCH 14/14] ateom-cloud-hypervisor: adopt upstream veth actor networking Port the kata runtime to main's veth model (mirrors the updated cmd/ateom-gvisor): a fresh per-activation veth pair with the worker side (ateom0) staying in the pod netns and the peer renamed eth0 in the interior netns at the stable actor address 169.254.17.2/30, plus nftables masquerade + pod-IP:80 DNAT. kata's tcfilter consumes the interior netns exactly like a CNI-provisioned container netns. Because the actor address is now constant, a restored guest's frozen network config is valid on every pod, which retires the whole post-restore re-IP mechanism (kata-agent UpdateInterface client + wire-mirror protos) and the eth0 move/scrape/restore machinery. One kata-specific delta from gVisor: the host-side veth gets a FIXED MAC, because a CH snapshot freezes the guest's ARP cache entry for the gateway (gVisor rebuilds its netstack on restore; a full-VM snapshot does not). Also adapts to upstream's TracingOptions (NoExporter removed). --- .../internal/kata/agentclient.go | 102 --- .../internal/kata/agentpb/agent.pb.go | 588 ----------------- .../internal/kata/agentpb/agent.proto | 77 --- .../internal/kata/agentpb/gen.go | 17 - cmd/ateom-cloud-hypervisor/main.go | 30 +- cmd/ateom-cloud-hypervisor/net.go | 600 ++++++++++++------ cmd/ateom-cloud-hypervisor/run.go | 138 +--- .../service_integration_test.go | 2 +- 8 files changed, 426 insertions(+), 1128 deletions(-) delete mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go delete mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go delete mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto delete mode 100644 cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go b/cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go deleted file mode 100644 index 4f1f495c7..000000000 --- a/cmd/ateom-cloud-hypervisor/internal/kata/agentclient.go +++ /dev/null @@ -1,102 +0,0 @@ -//go:build linux - -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kata - -import ( - "bufio" - "context" - "fmt" - "net" - "strings" - "time" - - "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpb" - "github.com/containerd/ttrpc" -) - -// agentVsockPort is the guest port the kata-agent's ttrpc server listens on. -const agentVsockPort = 1024 - -// AgentClient is a thin ttrpc client for the kata-agent RPCs ateom drives -// directly (network reconfiguration after a CH snapshot restore). It dials the -// agent through CH's hybrid-vsock unix socket — the same channel the kata shim -// uses — so it works on a restored VM with no shim present. -type AgentClient struct { - conn net.Conn - client *ttrpc.Client -} - -// DialAgent connects to the kata-agent through the hybrid-vsock socket at -// vsockPath (VsockSocketPath(id)): plain-text "CONNECT " handshake with -// the VMM, then ttrpc over the stream. -func DialAgent(ctx context.Context, vsockPath string) (*AgentClient, error) { - d := net.Dialer{} - conn, err := d.DialContext(ctx, "unix", vsockPath) - if err != nil { - return nil, fmt.Errorf("dialing hybrid vsock %q: %w", vsockPath, err) - } - if dl, ok := ctx.Deadline(); ok { - _ = conn.SetDeadline(dl) - } else { - _ = conn.SetDeadline(time.Now().Add(10 * time.Second)) - } - if _, err := fmt.Fprintf(conn, "CONNECT %d\n", agentVsockPort); err != nil { - conn.Close() - return nil, fmt.Errorf("hybrid vsock CONNECT: %w", err) - } - line, err := bufio.NewReader(conn).ReadString('\n') - if err != nil { - conn.Close() - return nil, fmt.Errorf("hybrid vsock CONNECT response: %w", err) - } - if !strings.HasPrefix(line, "OK ") { - conn.Close() - return nil, fmt.Errorf("hybrid vsock CONNECT refused: %q", strings.TrimSpace(line)) - } - _ = conn.SetDeadline(time.Time{}) // ttrpc manages its own timeouts via ctx - return &AgentClient{conn: conn, client: ttrpc.NewClient(conn)}, nil -} - -// Close shuts the ttrpc client and underlying connection. -func (a *AgentClient) Close() error { - err := a.client.Close() - _ = a.conn.Close() - return err -} - -// UpdateInterface replaces the addresses (and mtu/name binding) of the guest -// interface named by iface.Name/Device with the provided spec, returning the -// resulting interface state. Mirrors grpc.AgentService/UpdateInterface. -func (a *AgentClient) UpdateInterface(ctx context.Context, iface *agentpb.Interface) (*agentpb.Interface, error) { - resp := &agentpb.Interface{} - if err := a.client.Call(ctx, "grpc.AgentService", "UpdateInterface", - &agentpb.UpdateInterfaceRequest{Interface: iface}, resp); err != nil { - return nil, fmt.Errorf("agent UpdateInterface: %w", err) - } - return resp, nil -} - -// UpdateRoutes replaces the guest routing table with the provided routes, -// returning the resulting routes. Mirrors grpc.AgentService/UpdateRoutes. -func (a *AgentClient) UpdateRoutes(ctx context.Context, routes []*agentpb.Route) ([]*agentpb.Route, error) { - resp := &agentpb.Routes{} - if err := a.client.Call(ctx, "grpc.AgentService", "UpdateRoutes", - &agentpb.UpdateRoutesRequest{Routes: &agentpb.Routes{Routes: routes}}, resp); err != nil { - return nil, fmt.Errorf("agent UpdateRoutes: %w", err) - } - return resp.GetRoutes(), nil -} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go deleted file mode 100644 index 73ab9afe8..000000000 --- a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.pb.go +++ /dev/null @@ -1,588 +0,0 @@ -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Minimal wire-compatible mirror of the kata-agent ttrpc API surface -// ateom-cloud-hypervisor needs after a CH snapshot restore: updating the -// restored guest's network interface and routes to the new pod's addresses. -// -// Field numbers and types mirror kata-containers 3.31.0 -// src/libs/protocols/protos/{agent,types}.proto (Apache-2.0). The service is -// NOT declared here; calls go through ttrpc.Client.Call with the literal -// method names ("grpc.AgentService", "UpdateInterface"/"UpdateRoutes") so no -// ttrpc codegen plugin is required. kata's messages live in proto packages -// "grpc" and "types"; only field numbers matter on the wire, so they are -// flattened into this single package (type registration names differ, which -// is fine — names are never transmitted). - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.11-devel -// protoc v4.25.3 -// source: agent.proto - -package agentpb - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type IPFamily int32 - -const ( - IPFamily_v4 IPFamily = 0 - IPFamily_v6 IPFamily = 1 -) - -// Enum value maps for IPFamily. -var ( - IPFamily_name = map[int32]string{ - 0: "v4", - 1: "v6", - } - IPFamily_value = map[string]int32{ - "v4": 0, - "v6": 1, - } -) - -func (x IPFamily) Enum() *IPFamily { - p := new(IPFamily) - *p = x - return p -} - -func (x IPFamily) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (IPFamily) Descriptor() protoreflect.EnumDescriptor { - return file_agent_proto_enumTypes[0].Descriptor() -} - -func (IPFamily) Type() protoreflect.EnumType { - return &file_agent_proto_enumTypes[0] -} - -func (x IPFamily) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use IPFamily.Descriptor instead. -func (IPFamily) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_rawDescGZIP(), []int{0} -} - -type IPAddress struct { - state protoimpl.MessageState `protogen:"open.v1"` - Family IPFamily `protobuf:"varint,1,opt,name=family,proto3,enum=ateomkataagent.IPFamily" json:"family,omitempty"` - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - Mask string `protobuf:"bytes,3,opt,name=mask,proto3" json:"mask,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *IPAddress) Reset() { - *x = IPAddress{} - mi := &file_agent_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *IPAddress) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*IPAddress) ProtoMessage() {} - -func (x *IPAddress) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use IPAddress.ProtoReflect.Descriptor instead. -func (*IPAddress) Descriptor() ([]byte, []int) { - return file_agent_proto_rawDescGZIP(), []int{0} -} - -func (x *IPAddress) GetFamily() IPFamily { - if x != nil { - return x.Family - } - return IPFamily_v4 -} - -func (x *IPAddress) GetAddress() string { - if x != nil { - return x.Address - } - return "" -} - -func (x *IPAddress) GetMask() string { - if x != nil { - return x.Mask - } - return "" -} - -type Interface struct { - state protoimpl.MessageState `protogen:"open.v1"` - Device string `protobuf:"bytes,1,opt,name=device,proto3" json:"device,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - IPAddresses []*IPAddress `protobuf:"bytes,3,rep,name=IPAddresses,proto3" json:"IPAddresses,omitempty"` - Mtu uint64 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"` - HwAddr string `protobuf:"bytes,5,opt,name=hwAddr,proto3" json:"hwAddr,omitempty"` - DevicePath string `protobuf:"bytes,6,opt,name=devicePath,proto3" json:"devicePath,omitempty"` - Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` - RawFlags uint32 `protobuf:"varint,8,opt,name=raw_flags,json=rawFlags,proto3" json:"raw_flags,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Interface) Reset() { - *x = Interface{} - mi := &file_agent_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Interface) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Interface) ProtoMessage() {} - -func (x *Interface) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Interface.ProtoReflect.Descriptor instead. -func (*Interface) Descriptor() ([]byte, []int) { - return file_agent_proto_rawDescGZIP(), []int{1} -} - -func (x *Interface) GetDevice() string { - if x != nil { - return x.Device - } - return "" -} - -func (x *Interface) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Interface) GetIPAddresses() []*IPAddress { - if x != nil { - return x.IPAddresses - } - return nil -} - -func (x *Interface) GetMtu() uint64 { - if x != nil { - return x.Mtu - } - return 0 -} - -func (x *Interface) GetHwAddr() string { - if x != nil { - return x.HwAddr - } - return "" -} - -func (x *Interface) GetDevicePath() string { - if x != nil { - return x.DevicePath - } - return "" -} - -func (x *Interface) GetType() string { - if x != nil { - return x.Type - } - return "" -} - -func (x *Interface) GetRawFlags() uint32 { - if x != nil { - return x.RawFlags - } - return 0 -} - -type Route struct { - state protoimpl.MessageState `protogen:"open.v1"` - Dest string `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"` - Gateway string `protobuf:"bytes,2,opt,name=gateway,proto3" json:"gateway,omitempty"` - Device string `protobuf:"bytes,3,opt,name=device,proto3" json:"device,omitempty"` - Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` - Scope uint32 `protobuf:"varint,5,opt,name=scope,proto3" json:"scope,omitempty"` - Family IPFamily `protobuf:"varint,6,opt,name=family,proto3,enum=ateomkataagent.IPFamily" json:"family,omitempty"` - Flags uint32 `protobuf:"varint,7,opt,name=flags,proto3" json:"flags,omitempty"` - Mtu uint32 `protobuf:"varint,8,opt,name=mtu,proto3" json:"mtu,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Route) Reset() { - *x = Route{} - mi := &file_agent_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Route) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Route) ProtoMessage() {} - -func (x *Route) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Route.ProtoReflect.Descriptor instead. -func (*Route) Descriptor() ([]byte, []int) { - return file_agent_proto_rawDescGZIP(), []int{2} -} - -func (x *Route) GetDest() string { - if x != nil { - return x.Dest - } - return "" -} - -func (x *Route) GetGateway() string { - if x != nil { - return x.Gateway - } - return "" -} - -func (x *Route) GetDevice() string { - if x != nil { - return x.Device - } - return "" -} - -func (x *Route) GetSource() string { - if x != nil { - return x.Source - } - return "" -} - -func (x *Route) GetScope() uint32 { - if x != nil { - return x.Scope - } - return 0 -} - -func (x *Route) GetFamily() IPFamily { - if x != nil { - return x.Family - } - return IPFamily_v4 -} - -func (x *Route) GetFlags() uint32 { - if x != nil { - return x.Flags - } - return 0 -} - -func (x *Route) GetMtu() uint32 { - if x != nil { - return x.Mtu - } - return 0 -} - -type Routes struct { - state protoimpl.MessageState `protogen:"open.v1"` - Routes []*Route `protobuf:"bytes,1,rep,name=Routes,proto3" json:"Routes,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Routes) Reset() { - *x = Routes{} - mi := &file_agent_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Routes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Routes) ProtoMessage() {} - -func (x *Routes) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Routes.ProtoReflect.Descriptor instead. -func (*Routes) Descriptor() ([]byte, []int) { - return file_agent_proto_rawDescGZIP(), []int{3} -} - -func (x *Routes) GetRoutes() []*Route { - if x != nil { - return x.Routes - } - return nil -} - -type UpdateInterfaceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Interface *Interface `protobuf:"bytes,1,opt,name=interface,proto3" json:"interface,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateInterfaceRequest) Reset() { - *x = UpdateInterfaceRequest{} - mi := &file_agent_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdateInterfaceRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateInterfaceRequest) ProtoMessage() {} - -func (x *UpdateInterfaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdateInterfaceRequest.ProtoReflect.Descriptor instead. -func (*UpdateInterfaceRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_rawDescGZIP(), []int{4} -} - -func (x *UpdateInterfaceRequest) GetInterface() *Interface { - if x != nil { - return x.Interface - } - return nil -} - -type UpdateRoutesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Routes *Routes `protobuf:"bytes,1,opt,name=routes,proto3" json:"routes,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateRoutesRequest) Reset() { - *x = UpdateRoutesRequest{} - mi := &file_agent_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpdateRoutesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateRoutesRequest) ProtoMessage() {} - -func (x *UpdateRoutesRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdateRoutesRequest.ProtoReflect.Descriptor instead. -func (*UpdateRoutesRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_rawDescGZIP(), []int{5} -} - -func (x *UpdateRoutesRequest) GetRoutes() *Routes { - if x != nil { - return x.Routes - } - return nil -} - -var File_agent_proto protoreflect.FileDescriptor - -const file_agent_proto_rawDesc = "" + - "\n" + - "\vagent.proto\x12\x0eateomkataagent\"k\n" + - "\tIPAddress\x120\n" + - "\x06family\x18\x01 \x01(\x0e2\x18.ateomkataagent.IPFamilyR\x06family\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x12\n" + - "\x04mask\x18\x03 \x01(\tR\x04mask\"\xef\x01\n" + - "\tInterface\x12\x16\n" + - "\x06device\x18\x01 \x01(\tR\x06device\x12\x12\n" + - "\x04name\x18\x02 \x01(\tR\x04name\x12;\n" + - "\vIPAddresses\x18\x03 \x03(\v2\x19.ateomkataagent.IPAddressR\vIPAddresses\x12\x10\n" + - "\x03mtu\x18\x04 \x01(\x04R\x03mtu\x12\x16\n" + - "\x06hwAddr\x18\x05 \x01(\tR\x06hwAddr\x12\x1e\n" + - "\n" + - "devicePath\x18\x06 \x01(\tR\n" + - "devicePath\x12\x12\n" + - "\x04type\x18\a \x01(\tR\x04type\x12\x1b\n" + - "\traw_flags\x18\b \x01(\rR\brawFlags\"\xd5\x01\n" + - "\x05Route\x12\x12\n" + - "\x04dest\x18\x01 \x01(\tR\x04dest\x12\x18\n" + - "\agateway\x18\x02 \x01(\tR\agateway\x12\x16\n" + - "\x06device\x18\x03 \x01(\tR\x06device\x12\x16\n" + - "\x06source\x18\x04 \x01(\tR\x06source\x12\x14\n" + - "\x05scope\x18\x05 \x01(\rR\x05scope\x120\n" + - "\x06family\x18\x06 \x01(\x0e2\x18.ateomkataagent.IPFamilyR\x06family\x12\x14\n" + - "\x05flags\x18\a \x01(\rR\x05flags\x12\x10\n" + - "\x03mtu\x18\b \x01(\rR\x03mtu\"7\n" + - "\x06Routes\x12-\n" + - "\x06Routes\x18\x01 \x03(\v2\x15.ateomkataagent.RouteR\x06Routes\"Q\n" + - "\x16UpdateInterfaceRequest\x127\n" + - "\tinterface\x18\x01 \x01(\v2\x19.ateomkataagent.InterfaceR\tinterface\"E\n" + - "\x13UpdateRoutesRequest\x12.\n" + - "\x06routes\x18\x01 \x01(\v2\x16.ateomkataagent.RoutesR\x06routes*\x1a\n" + - "\bIPFamily\x12\x06\n" + - "\x02v4\x10\x00\x12\x06\n" + - "\x02v6\x10\x01BWZUgithub.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpbb\x06proto3" - -var ( - file_agent_proto_rawDescOnce sync.Once - file_agent_proto_rawDescData []byte -) - -func file_agent_proto_rawDescGZIP() []byte { - file_agent_proto_rawDescOnce.Do(func() { - file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc))) - }) - return file_agent_proto_rawDescData -} - -var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 6) -var file_agent_proto_goTypes = []any{ - (IPFamily)(0), // 0: ateomkataagent.IPFamily - (*IPAddress)(nil), // 1: ateomkataagent.IPAddress - (*Interface)(nil), // 2: ateomkataagent.Interface - (*Route)(nil), // 3: ateomkataagent.Route - (*Routes)(nil), // 4: ateomkataagent.Routes - (*UpdateInterfaceRequest)(nil), // 5: ateomkataagent.UpdateInterfaceRequest - (*UpdateRoutesRequest)(nil), // 6: ateomkataagent.UpdateRoutesRequest -} -var file_agent_proto_depIdxs = []int32{ - 0, // 0: ateomkataagent.IPAddress.family:type_name -> ateomkataagent.IPFamily - 1, // 1: ateomkataagent.Interface.IPAddresses:type_name -> ateomkataagent.IPAddress - 0, // 2: ateomkataagent.Route.family:type_name -> ateomkataagent.IPFamily - 3, // 3: ateomkataagent.Routes.Routes:type_name -> ateomkataagent.Route - 2, // 4: ateomkataagent.UpdateInterfaceRequest.interface:type_name -> ateomkataagent.Interface - 4, // 5: ateomkataagent.UpdateRoutesRequest.routes:type_name -> ateomkataagent.Routes - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name -} - -func init() { file_agent_proto_init() } -func file_agent_proto_init() { - if File_agent_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)), - NumEnums: 1, - NumMessages: 6, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_agent_proto_goTypes, - DependencyIndexes: file_agent_proto_depIdxs, - EnumInfos: file_agent_proto_enumTypes, - MessageInfos: file_agent_proto_msgTypes, - }.Build() - File_agent_proto = out.File - file_agent_proto_goTypes = nil - file_agent_proto_depIdxs = nil -} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto deleted file mode 100644 index 029120fa3..000000000 --- a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/agent.proto +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Minimal wire-compatible mirror of the kata-agent ttrpc API surface -// ateom-cloud-hypervisor needs after a CH snapshot restore: updating the -// restored guest's network interface and routes to the new pod's addresses. -// -// Field numbers and types mirror kata-containers 3.31.0 -// src/libs/protocols/protos/{agent,types}.proto (Apache-2.0). The service is -// NOT declared here; calls go through ttrpc.Client.Call with the literal -// method names ("grpc.AgentService", "UpdateInterface"/"UpdateRoutes") so no -// ttrpc codegen plugin is required. kata's messages live in proto packages -// "grpc" and "types"; only field numbers matter on the wire, so they are -// flattened into this single package (type registration names differ, which -// is fine — names are never transmitted). - -syntax = "proto3"; - -package ateomkataagent; - -option go_package = "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpb"; - -enum IPFamily { - v4 = 0; - v6 = 1; -} - -message IPAddress { - IPFamily family = 1; - string address = 2; - string mask = 3; -} - -message Interface { - string device = 1; - string name = 2; - repeated IPAddress IPAddresses = 3; - uint64 mtu = 4; - string hwAddr = 5; - string devicePath = 6; - string type = 7; - uint32 raw_flags = 8; -} - -message Route { - string dest = 1; - string gateway = 2; - string device = 3; - string source = 4; - uint32 scope = 5; - IPFamily family = 6; - uint32 flags = 7; - uint32 mtu = 8; -} - -message Routes { - repeated Route Routes = 1; -} - -message UpdateInterfaceRequest { - Interface interface = 1; -} - -message UpdateRoutesRequest { - Routes routes = 1; -} diff --git a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go b/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go deleted file mode 100644 index 6165bfd62..000000000 --- a/cmd/ateom-cloud-hypervisor/internal/kata/agentpb/gen.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package agentpb - -//go:generate bash -c "../../../../../hack/protoc.sh --plugin=protoc-gen-go=$(bash ../../../../../hack/run-tool.sh --print-bin-path protoc-gen-go) --go_out=paths=source_relative:. agent.proto" diff --git a/cmd/ateom-cloud-hypervisor/main.go b/cmd/ateom-cloud-hypervisor/main.go index bec8d68c3..1785d4315 100644 --- a/cmd/ateom-cloud-hypervisor/main.go +++ b/cmd/ateom-cloud-hypervisor/main.go @@ -38,7 +38,6 @@ import ( "github.com/agent-substrate/substrate/internal/serverboot" "github.com/agent-substrate/substrate/internal/version" "github.com/hashicorp/go-reap" - "github.com/vishvananda/netlink" "github.com/vishvananda/netns" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" sdktrace "go.opentelemetry.io/otel/sdk/trace" @@ -94,9 +93,6 @@ func do(ctx context.Context) error { tp, err := serverboot.InitTracing(ctx, serverboot.TracingOptions{ ServiceName: "ateom-cloud-hypervisor", Sampler: sdktrace.ParentBased(sdktrace.NeverSample()), - // The micro-VM keeps the pod network, but for the POC we have no trace - // collector to export to; skip the exporter like ateom-gvisor. - NoExporter: true, }) if err != nil { serverboot.Fatal(ctx, "Failed to initialize tracing", err) @@ -142,18 +138,9 @@ func do(ctx context.Context) error { return fmt.Errorf("while opening unix socket: %w", err) } - // Networking (mirrors ateom-gvisor): scrape the pod's eth0, then create a - // named interior netns. RunWorkload moves eth0 into it (addrs/routes restored) - // and points kata at it so the micro-VM gets the pod's network. - eth0Link, err := netlink.LinkByName("eth0") - if err != nil { - return fmt.Errorf("while getting netlink link for eth0: %w", err) - } - eth0LinkInfo, err := scrapeLink(eth0Link) - if err != nil { - return fmt.Errorf("while scraping info from eth0: %w", err) - } - slog.InfoContext(ctx, "Scraped eth0", slog.Any("eth0", eth0LinkInfo)) + // Networking (mirrors ateom-gvisor's veth model): create a named interior + // netns; each activation builds a fresh veth pair into it (see net.go) and + // points kata at it. interiorNetNS, err := createNetNSWithoutSwitching(ateompath.AteomNetNSName(*podUID)) if err != nil { return fmt.Errorf("while creating interior netns: %w", err) @@ -163,7 +150,7 @@ func do(ctx context.Context) error { grpc.StatsHandler(otelgrpc.NewServerHandler()), grpc.UnaryInterceptor(ateinterceptors.ServerUnaryInterceptor), ) - ateompb.RegisterAteomServer(svr, NewService(*podUID, *shimBinary, *chBinary, *virtiofsdBinary, *kataConfig, *kataNamespace, *kataDebug, interiorNetNS, eth0LinkInfo)) + ateompb.RegisterAteomServer(svr, NewService(*podUID, *shimBinary, *chBinary, *virtiofsdBinary, *kataConfig, *kataNamespace, *kataDebug, interiorNetNS)) reflection.Register(svr) slog.InfoContext(ctx, "ateom-cloud-hypervisor serving", slog.String("socket", sockPath)) @@ -239,11 +226,9 @@ type AteomService struct { namespace string kataDebug bool - // interiorNetNS holds the pod's eth0 while a workload runs (mirrors - // ateom-gvisor); kata is pointed at it. eth0LinkInfo is eth0's scraped - // addrs/routes, restored after moving the link into the interior netns. + // interiorNetNS hosts the per-activation actor veth peer (see net.go); + // kata is pointed at it. interiorNetNS netns.NsHandle - eth0LinkInfo *SaveLinkInfo // running maps actor id -> the live micro-VM, kept so CheckpointWorkload can // pause+snapshot+teardown the same sandbox (and RestoreWorkload can track the @@ -254,7 +239,7 @@ type AteomService struct { var _ ateompb.AteomServer = (*AteomService)(nil) // NewService creates a new AteomService. -func NewService(podUID, shimBinary, chBinary, virtiofsdBinary, kataConfig, namespace string, kataDebug bool, interiorNetNS netns.NsHandle, eth0LinkInfo *SaveLinkInfo) *AteomService { +func NewService(podUID, shimBinary, chBinary, virtiofsdBinary, kataConfig, namespace string, kataDebug bool, interiorNetNS netns.NsHandle) *AteomService { return &AteomService{ podUID: podUID, shimBinary: shimBinary, @@ -264,7 +249,6 @@ func NewService(podUID, shimBinary, chBinary, virtiofsdBinary, kataConfig, names namespace: namespace, kataDebug: kataDebug, interiorNetNS: interiorNetNS, - eth0LinkInfo: eth0LinkInfo, running: map[string]*runningActor{}, } } diff --git a/cmd/ateom-cloud-hypervisor/net.go b/cmd/ateom-cloud-hypervisor/net.go index 2d7ca1b97..8b674c989 100644 --- a/cmd/ateom-cloud-hypervisor/net.go +++ b/cmd/ateom-cloud-hypervisor/net.go @@ -16,123 +16,434 @@ package main -// Networking mirrors cmd/ateom-gvisor: the pod's eth0 is moved into a named -// "interior" network namespace (its addresses/routes restored after the move), -// and the sandbox is pointed at that netns. gVisor reads the link via AF_PACKET; -// kata instead finds eth0 in the netns and builds a tap + TC mirror for the VM's -// virtio-net. (Copied with light adaptation; expected to be de-duplicated into a -// shared package on a later rebase.) +// Actor networking mirrors cmd/ateom-gvisor's veth model: a fresh +// point-to-point veth pair per activation, with the worker side (ateom0, +// 169.254.17.1/30) staying in the pod netns next to the pod's real eth0, and +// the peer moved into the interior netns, renamed eth0, and given the stable +// actor address 169.254.17.2/30. nftables rules in the pod netns masquerade +// actor egress behind the pod IP and DNAT inbound pod-IP:80 to the actor. +// +// kata consumes the interior netns exactly like a CNI-provisioned container +// netns: its tcfilter network model builds a tap cross-connected to eth0 (the +// veth peer) and gives the guest eth0's address. Because that address is a +// CONSTANT, a restored guest's frozen network config stays valid on any pod — +// no in-guest reconfiguration needed. +// +// (Copied with light adaptation from cmd/ateom-gvisor; expected to be +// de-duplicated into a shared package later.) import ( "context" + "errors" "fmt" "log/slog" "net" "os" "runtime" - "sort" + "github.com/google/nftables" + "github.com/google/nftables/binaryutil" + "github.com/google/nftables/expr" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" "golang.org/x/sys/unix" + + "github.com/agent-substrate/substrate/internal/serverboot" ) -// SaveLinkInfo captures a link's addresses and routes so they can be restored -// after the link is moved into another network namespace (which drops them). -type SaveLinkInfo struct { - Addresses []SaveAddr - Routes []SaveRoute - MTU int -} +const ( + hostVethName = "ateom0" + actorVethName = "eth0" + actorVethTempName = "ateom1" + hostVethCIDR = "169.254.17.1/30" + actorVethCIDR = "169.254.17.2/30" + actorVethGateway = "169.254.17.1" + actorVethIP = "169.254.17.2" + actorNftTableName = "ateom_actor" -type SaveAddr struct { - Addr net.IPNet - Scope int - Broadcast net.IP -} + // hostVethMAC is deliberately FIXED (locally administered), unlike + // ateom-gvisor where the kernel's random veth MAC is fine. A CH snapshot + // freezes the guest kernel's ARP cache, including the entry for the + // gateway 169.254.17.1; restoring against a new veth pair with a random + // MAC would blackhole guest egress until that entry expires. A constant + // gateway MAC keeps the frozen entry valid on every pod. + hostVethMAC = "02:a8:1e:00:00:01" +) + +// setupActorNetwork builds a fresh point-to-point network between the worker +// pod netns and the kata interior netns (see the package comment). Idempotent +// via cleanup-before-setup; also sweeps stale kata taps out of the interior +// netns so the sandbox always builds on a clean slate. +func (s *AteomService) setupActorNetwork(ctx context.Context) (retErr error) { + s.cleanupActorNetworkOrExit(ctx, "Failed to clean up stale actor network before setup") + defer func() { + if retErr != nil { + s.cleanupActorNetworkOrExit(ctx, "Failed to clean up partially configured actor network") + } + }() -type SaveRoute struct { - Scope uint8 - Dst net.IPNet - Src net.IP - Gateway net.IP - Protocol int - Type int + podIP, err := podIPv4() + if err != nil { + return fmt.Errorf("while resolving pod IPv4 address: %w", err) + } + + // Sweep leftover links (kata taps from torn-down runs, restore taps) from + // the persistent interior netns before the new veth peer arrives. + if err := netNSDo(ctx, s.interiorNetNS, func(ctx context.Context) error { + links, err := netlink.LinkList() + if err != nil { + return fmt.Errorf("while listing interior netns links: %w", err) + } + for _, l := range links { + if l.Attrs().Name == "lo" { + continue + } + if err := netlink.LinkDel(l); err != nil { + slog.WarnContext(ctx, "Failed to delete leftover interior link", slog.String("link", l.Attrs().Name), slog.Any("err", err)) + } + } + return nil + }); err != nil { + return err + } + + hostAddr, err := parseAddr(hostVethCIDR) + if err != nil { + return err + } + hostMAC, err := net.ParseMAC(hostVethMAC) + if err != nil { + return fmt.Errorf("while parsing host veth MAC: %w", err) + } + + veth := &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{ + Name: hostVethName, + HardwareAddr: hostMAC, + }, + PeerName: actorVethTempName, + } + if err := netlink.LinkAdd(veth); err != nil { + return fmt.Errorf("while creating actor veth pair: %w", err) + } + + hostLink, err := netlink.LinkByName(hostVethName) + if err != nil { + return fmt.Errorf("while getting host veth: %w", err) + } + if err := netlink.AddrReplace(hostLink, hostAddr); err != nil { + return fmt.Errorf("while assigning host veth address: %w", err) + } + if err := netlink.LinkSetUp(hostLink); err != nil { + return fmt.Errorf("while bringing up host veth: %w", err) + } + + actorLink, err := netlink.LinkByName(actorVethTempName) + if err != nil { + return fmt.Errorf("while getting actor veth peer: %w", err) + } + if err := netlink.LinkSetNsFd(actorLink, int(s.interiorNetNS)); err != nil { + return fmt.Errorf("while moving actor veth peer into interior netns: %w", err) + } + + if err := netNSDo(ctx, s.interiorNetNS, configureActorVeth); err != nil { + return fmt.Errorf("while configuring actor veth in interior netns: %w", err) + } + + if err := enableIPv4Forwarding(); err != nil { + return err + } + if err := installActorNftablesRules(podIP); err != nil { + return err + } + + return nil } -func scrapeLink(link netlink.Link) (*SaveLinkInfo, error) { - rawAddrs, err := netlink.AddrList(link, netlink.FAMILY_ALL) +func configureActorVeth(ctx context.Context) error { + // Run inside the interior netns after setupActorNetwork moves the veth peer + // there. kata reads link names, addresses, and routes from this namespace + // when the sandbox starts, so the peer is renamed to eth0 and configured + // like a normal container interface; the guest is configured identically by + // the kata agent. + loLink, err := netlink.LinkByName("lo") if err != nil { - return nil, fmt.Errorf("while scraping addresses: %w", err) + return fmt.Errorf("while acquiring lo in interior netns: %w", err) } - var addrs []SaveAddr - for _, rawAddr := range rawAddrs { - addrs = append(addrs, SaveAddr{ - Addr: *rawAddr.IPNet, - Scope: rawAddr.Scope, - Broadcast: rawAddr.Broadcast, - }) + if err := netlink.LinkSetUp(loLink); err != nil { + return fmt.Errorf("while bringing up lo in interior netns: %w", err) } - rawRoutes, err := netlink.RouteList(link, netlink.FAMILY_ALL) + actorLink, err := netlink.LinkByName(actorVethTempName) if err != nil { - return nil, fmt.Errorf("while scraping routes: %w", err) + return fmt.Errorf("while acquiring actor veth in interior netns: %w", err) + } + if err := netlink.LinkSetName(actorLink, actorVethName); err != nil { + return fmt.Errorf("while renaming actor veth to %q: %w", actorVethName, err) + } + actorLink, err = netlink.LinkByName(actorVethName) + if err != nil { + return fmt.Errorf("while reacquiring actor veth in interior netns: %w", err) + } + + actorAddr, err := parseAddr(actorVethCIDR) + if err != nil { + return err + } + if err := netlink.AddrReplace(actorLink, actorAddr); err != nil { + return fmt.Errorf("while assigning actor veth address: %w", err) + } + if err := netlink.LinkSetUp(actorLink); err != nil { + return fmt.Errorf("while bringing up actor veth: %w", err) + } + + gw := net.ParseIP(actorVethGateway).To4() + if gw == nil { + return fmt.Errorf("invalid actor veth gateway %q", actorVethGateway) + } + if err := netlink.RouteReplace(&netlink.Route{ + LinkIndex: actorLink.Attrs().Index, + Gw: gw, + }); err != nil { + return fmt.Errorf("while installing actor default route: %w", err) + } + + return nil +} + +// cleanupActorNetwork removes all per-activation network state owned by ateom. +// Intentionally idempotent: runs before setup, after checkpoint, and from +// setup-failure cleanup. +func (s *AteomService) cleanupActorNetwork(ctx context.Context) error { + if err := removeActorNftablesRules(); err != nil { + return err + } + + var cleanupErr error + if link, err := netlink.LinkByName(hostVethName); err == nil { + if err := netlink.LinkDel(link); err != nil { + cleanupErr = errors.Join(cleanupErr, fmt.Errorf("while deleting host veth: %w", err)) + slog.WarnContext(ctx, "Failed to delete host veth; continuing actor netns cleanup", slog.Any("err", err)) + } + } else if _, ok := err.(netlink.LinkNotFoundError); !ok { + cleanupErr = errors.Join(cleanupErr, fmt.Errorf("while looking up host veth: %w", err)) + slog.WarnContext(ctx, "Failed to look up host veth; continuing actor netns cleanup", slog.Any("err", err)) } - var routes []SaveRoute - for _, rawRoute := range rawRoutes { - dst := net.IPNet{} - if rawRoute.Dst != nil { - dst = *rawRoute.Dst + + if err := netNSDo(ctx, s.interiorNetNS, func(_ context.Context) error { + for _, name := range []string{actorVethName, actorVethTempName} { + link, err := netlink.LinkByName(name) + if err == nil { + if err := netlink.LinkDel(link); err != nil { + return fmt.Errorf("while deleting interior veth %q: %w", name, err) + } + continue + } + if _, ok := err.(netlink.LinkNotFoundError); !ok { + return fmt.Errorf("while looking up interior veth %q: %w", name, err) + } } - routes = append(routes, SaveRoute{ - Scope: uint8(rawRoute.Scope), - Dst: dst, - Src: rawRoute.Src, - Gateway: rawRoute.Gw, - Protocol: int(rawRoute.Protocol), - Type: rawRoute.Type, - }) + return nil + }); err != nil { + cleanupErr = errors.Join(cleanupErr, fmt.Errorf("while cleaning interior netns links: %w", err)) } - return &SaveLinkInfo{Addresses: addrs, Routes: routes, MTU: link.Attrs().MTU}, nil + return cleanupErr } -func restoreLink(ctx context.Context, link netlink.Link, info *SaveLinkInfo) error { - for i, saveAddr := range info.Addresses { - addr := &netlink.Addr{ - IPNet: &saveAddr.Addr, - Scope: saveAddr.Scope, - Broadcast: saveAddr.Broadcast, +func (s *AteomService) cleanupActorNetworkOrExit(ctx context.Context, msg string) { + if err := s.cleanupActorNetwork(ctx); err != nil { + serverboot.Fatal(ctx, msg, err) + } +} + +// podIPv4 resolves the worker pod IPv4 address from the pod namespace's real +// eth0 (which stays in the pod namespace in the veth model). +func podIPv4() (net.IP, error) { + eth0Link, err := netlink.LinkByName("eth0") + if err != nil { + return nil, fmt.Errorf("while getting pod eth0: %w", err) + } + addrs, err := netlink.AddrList(eth0Link, netlink.FAMILY_V4) + if err != nil { + return nil, fmt.Errorf("while listing pod eth0 addresses: %w", err) + } + for _, addr := range addrs { + if addr.IP == nil { + continue } - if err := netlink.AddrReplace(link, addr); err != nil { - return fmt.Errorf("while restoring addr %d onto link: %w", i, err) + if ip := addr.IP.To4(); ip != nil { + return ip, nil } } - // Link-scope routes must be installed before gateway routes so the kernel - // can resolve each gateway's nexthop (fib_check_nh_v4_gw). - routes := append([]SaveRoute(nil), info.Routes...) - sort.SliceStable(routes, func(i, j int) bool { - return routes[i].Gateway == nil && routes[j].Gateway != nil + return nil, fmt.Errorf("pod eth0 has no IPv4 address") +} + +func parseAddr(cidr string) (*netlink.Addr, error) { + addr, err := netlink.ParseAddr(cidr) + if err != nil { + return nil, fmt.Errorf("while parsing address %q: %w", cidr, err) + } + return addr, nil +} + +func enableIPv4Forwarding() error { + // Actor packets enter the worker pod via the host-side veth and leave + // through the pod's eth0; the kernel will not route between them otherwise. + if err := os.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0o644); err != nil { + return fmt.Errorf("while enabling IPv4 forwarding in worker pod netns: %w", err) + } + return nil +} + +func installActorNftablesRules(podIP net.IP) error { + // Dedicated ateom-owned IPv4 table (cheap cleanup, no CNI chain mutation): + // * postrouting: masquerade actor egress (169.254.17.2) behind the pod IP. + // * prerouting: DNAT pod-IP:80/tcp to the actor veth IP. + // * forward: accept forwarded packets between the actor veth and pod eth0. + // Mirrors cmd/ateom-gvisor (same compatibility-bridge caveats and TODOs). + if err := removeActorNftablesRules(); err != nil { + return err + } + + c := &nftables.Conn{} + table := &nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: actorNftTableName, + } + c.AddTable(table) + + prerouting := c.AddChain(&nftables.Chain{ + Name: "prerouting", + Table: table, + Type: nftables.ChainTypeNAT, + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityNATDest, + }) + preroutingExprs := append(ipDestinationEqual(podIP.String()), tcpDestinationPortEqual(80)...) + preroutingExprs = append(preroutingExprs, + &expr.Immediate{ + Register: 1, + Data: net.ParseIP(actorVethIP).To4(), + }, + &expr.Immediate{ + Register: 2, + Data: binaryutil.BigEndian.PutUint16(80), + }, + &expr.NAT{ + Type: expr.NATTypeDestNAT, + Family: unix.NFPROTO_IPV4, + RegAddrMin: 1, + RegProtoMin: 2, + }, + ) + c.AddRule(&nftables.Rule{ + Table: table, + Chain: prerouting, + Exprs: preroutingExprs, + }) + + postrouting := c.AddChain(&nftables.Chain{ + Name: "postrouting", + Table: table, + Type: nftables.ChainTypeNAT, + Hooknum: nftables.ChainHookPostrouting, + Priority: nftables.ChainPriorityNATSource, + }) + c.AddRule(&nftables.Rule{ + Table: table, + Chain: postrouting, + Exprs: append(ipSourceEqual(actorVethIP), &expr.Masq{}), + }) + + acceptPolicy := nftables.ChainPolicyAccept + forward := c.AddChain(&nftables.Chain{ + Name: "forward", + Table: table, + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookForward, + Priority: nftables.ChainPriorityFilter, + Policy: &acceptPolicy, + }) + c.AddRule(&nftables.Rule{ + Table: table, + Chain: forward, + Exprs: []expr.Any{ + &expr.Verdict{Kind: expr.VerdictAccept}, + }, }) - for i, saveRoute := range routes { - dst := saveRoute.Dst - route := &netlink.Route{ - LinkIndex: link.Attrs().Index, - Scope: netlink.Scope(saveRoute.Scope), - Dst: &dst, - Src: saveRoute.Src, - Gw: saveRoute.Gateway, - Protocol: netlink.RouteProtocol(saveRoute.Protocol), - Type: saveRoute.Type, + + if err := c.Flush(); err != nil { + return fmt.Errorf("while installing actor nftables rules: %w", err) + } + return nil +} + +func removeActorNftablesRules() error { + c := &nftables.Conn{} + tables, err := c.ListTablesOfFamily(nftables.TableFamilyIPv4) + if err != nil { + return fmt.Errorf("while listing nftables tables: %w", err) + } + for _, table := range tables { + if table.Name != actorNftTableName { + continue } - slog.InfoContext(ctx, "Restoring route", slog.String("dst", dst.String()), slog.Any("gateway", saveRoute.Gateway)) - if err := netlink.RouteReplace(route); err != nil { - return fmt.Errorf("while restoring route %d: %w", i, err) + c.DelTable(table) + if err := c.Flush(); err != nil { + return fmt.Errorf("while deleting actor nftables table: %w", err) } + return nil } return nil } +func ipSourceEqual(ip string) []expr.Any { + return ipPayloadEqual(12, ip) +} + +func ipDestinationEqual(ip string) []expr.Any { + return ipPayloadEqual(16, ip) +} + +func ipPayloadEqual(offset uint32, ip string) []expr.Any { + return []expr.Any{ + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: offset, + Len: 4, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: net.ParseIP(ip).To4(), + }, + } +} + +func tcpDestinationPortEqual(port uint16) []expr.Any { + return []expr.Any{ + &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{unix.IPPROTO_TCP}, + }, + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, + Len: 2, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(port), + }, + } +} + // createNetNSWithoutSwitching creates a named netns and returns its handle, // restoring the caller's current netns before returning. func createNetNSWithoutSwitching(name string) (netns.NsHandle, error) { @@ -180,85 +491,19 @@ func netNSDo(ctx context.Context, targetNS netns.NsHandle, do func(context.Conte return nil } -// moveEth0IntoInteriorNetns moves the pod's eth0 into the interior netns and -// restores its addresses/routes there. After this, the interior netns holds a -// fully configured eth0 the sandbox runtime can consume. -func (s *AteomService) moveEth0IntoInteriorNetns(ctx context.Context) error { - eth0Link, err := netlink.LinkByName("eth0") - if err != nil { - return fmt.Errorf("while getting netlink link for eth0: %w", err) - } - if err := netlink.LinkSetNsFd(eth0Link, int(s.interiorNetNS)); err != nil { - return fmt.Errorf("while moving eth0 into interior network namespace: %w", err) - } - return netNSDo(ctx, s.interiorNetNS, func(ctx context.Context) error { - // The interior netns is persistent (created once at boot). kata's tcfilter - // model creates a tap (and may leave it behind when a failed run is torn - // down by driving the shim directly). A leftover tap with the same name/ - // index makes the next run's qdisc add fail "Failed to add qdisc ... file - // exists". Delete every link that isn't lo or the eth0 we're about to move - // in, so kata always builds its network on a clean slate. - if links, lerr := netlink.LinkList(); lerr == nil { - for _, l := range links { - name := l.Attrs().Name - if name == "lo" || name == "eth0" { - continue - } - if delErr := netlink.LinkDel(l); delErr != nil { - slog.WarnContext(ctx, "Failed to delete leftover link in interior netns", slog.String("link", name), slog.Any("err", delErr)) - } else { - slog.InfoContext(ctx, "Deleted leftover link in interior netns", slog.String("link", name)) - } - } - } - loLink, err := netlink.LinkByName("lo") - if err != nil { - return fmt.Errorf("while acquiring lo in interior netns: %w", err) - } - if err := netlink.LinkSetUp(loLink); err != nil { - return fmt.Errorf("while bringing up lo in interior netns: %w", err) - } - link, err := netlink.LinkByName("eth0") - if err != nil { - return fmt.Errorf("while acquiring eth0 in interior netns: %w", err) - } - if err := netlink.LinkSetUp(link); err != nil { - return fmt.Errorf("while bringing up eth0 in interior netns: %w", err) - } - if err := restoreLink(ctx, link, s.eth0LinkInfo); err != nil { - return fmt.Errorf("while restoring eth0 routes/addresses in interior netns: %w", err) - } - // kata's tcfilter network model adds a clsact qdisc to eth0; a stale one - // (left by a prior failed attempt, carried across the netns move) makes - // kata's add fail "Failed to add qdisc ... file exists". Remove any - // clsact/ingress qdisc so kata can add its own. - if qdiscs, qerr := netlink.QdiscList(link); qerr == nil { - for _, q := range qdiscs { - if t := q.Type(); t == "clsact" || t == "ingress" { - if delErr := netlink.QdiscDel(q); delErr != nil { - slog.WarnContext(ctx, "Failed to delete stale qdisc on eth0", slog.String("type", t), slog.Any("err", delErr)) - } else { - slog.InfoContext(ctx, "Removed stale qdisc on eth0", slog.String("type", t)) - } - } - } - } - return nil - }) -} - // setupRestoreTap recreates, in the interior netns, the tap + TC-mirror wiring // kata's tcfilter network model builds at boot: a tap device cross-connected to -// eth0 with mirred-redirect ingress filters in both directions. Returns the -// open tap FDs (one per queue pair) for cloud-hypervisor to adopt via -// vm.restore net_fds (the snapshot's virtio-net device is fd-backed, so CH -// requires fresh FDs on restore). Call after moveEth0IntoInteriorNetns. +// eth0 (the actor veth peer) with mirred-redirect ingress filters in both +// directions. Returns the open tap FDs (one per queue pair) for +// cloud-hypervisor to adopt via vm.restore net_fds (the snapshot's virtio-net +// device is fd-backed, so CH requires fresh FDs on restore). Call after +// setupActorNetwork. func (s *AteomService) setupRestoreTap(ctx context.Context, name string, queuePairs int) ([]*os.File, error) { var fds []*os.File err := netNSDo(ctx, s.interiorNetNS, func(ctx context.Context) error { - eth0, err := netlink.LinkByName("eth0") + eth0, err := netlink.LinkByName(actorVethName) if err != nil { - return fmt.Errorf("acquiring eth0 in interior netns: %w", err) + return fmt.Errorf("acquiring actor veth in interior netns: %w", err) } if old, lerr := netlink.LinkByName(name); lerr == nil { _ = netlink.LinkDel(old) @@ -280,9 +525,9 @@ func (s *AteomService) setupRestoreTap(ctx context.Context, name string, queuePa if err := netlink.LinkSetUp(tap); err != nil { return fmt.Errorf("bringing up tap %q: %w", name, err) } - // Cross-connect: everything arriving on eth0 redirects out the tap and - // vice versa (kata's TCFilterModel: ingress qdisc + match-all u32 with a - // mirred egress-redirect action, here via U32.RedirIndex). + // Cross-connect: everything arriving on the veth peer redirects out the + // tap and vice versa (kata's TCFilterModel: ingress qdisc + match-all u32 + // with a mirred egress-redirect action, here via U32.RedirIndex). for _, pair := range [][2]netlink.Link{{eth0, tap}, {tap, eth0}} { qdisc := &netlink.Ingress{QdiscAttrs: netlink.QdiscAttrs{ LinkIndex: pair[0].Attrs().Index, @@ -316,50 +561,3 @@ func (s *AteomService) setupRestoreTap(ctx context.Context, name string, queuePa } return fds, nil } - -// ensureEth0InPodNetns moves eth0 back to the pod netns if a prior Run/Restore -// left it in the interior netns. Idempotent. -func (s *AteomService) ensureEth0InPodNetns(ctx context.Context) error { - if _, err := netlink.LinkByName("eth0"); err == nil { - return nil - } - podNetNS, err := netns.Get() - if err != nil { - return fmt.Errorf("while getting pod netns: %w", err) - } - var moved bool - err = netNSDo(ctx, s.interiorNetNS, func(_ context.Context) error { - link, lookupErr := netlink.LinkByName("eth0") - if lookupErr != nil { - return nil - } - if mvErr := netlink.LinkSetNsFd(link, int(podNetNS)); mvErr != nil { - return fmt.Errorf("while moving eth0 to pod netns: %w", mvErr) - } - moved = true - return nil - }) - if moved { - slog.WarnContext(ctx, "Recovered eth0 from interior netns to pod netns") - } - return err -} - -// returnEth0ToPodNetns moves eth0 back from the interior netns to the pod netns -// (called after a workload is torn down). -func (s *AteomService) returnEth0ToPodNetns(ctx context.Context) error { - podNetNS, err := netns.Get() - if err != nil { - return fmt.Errorf("while getting pod netns: %w", err) - } - return netNSDo(ctx, s.interiorNetNS, func(_ context.Context) error { - link, lookupErr := netlink.LinkByName("eth0") - if lookupErr != nil { - return nil // already gone - } - if err := netlink.LinkSetNsFd(link, int(podNetNS)); err != nil { - return fmt.Errorf("while sending eth0 back to pod netns: %w", err) - } - return nil - }) -} diff --git a/cmd/ateom-cloud-hypervisor/run.go b/cmd/ateom-cloud-hypervisor/run.go index 9dfbeebdb..9c8de05d5 100644 --- a/cmd/ateom-cloud-hypervisor/run.go +++ b/cmd/ateom-cloud-hypervisor/run.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "log/slog" - "net" "os" "os/exec" "path/filepath" @@ -30,12 +29,10 @@ import ( "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/ch" "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata" - "github.com/agent-substrate/substrate/cmd/ateom-cloud-hypervisor/internal/kata/agentpb" "github.com/agent-substrate/substrate/internal/ateompath" "github.com/agent-substrate/substrate/internal/proto/ateompb" ctrtypes "github.com/containerd/containerd/api/types" "github.com/opencontainers/runtime-spec/specs-go" - "github.com/vishvananda/netlink" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -166,19 +163,16 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload } containerName := containers[0].GetName() - // Networking (mirrors ateom-gvisor): move the pod's eth0 into the interior - // netns (addrs/routes restored) and point kata at it. Roll back on failure so - // the pod isn't left without eth0 for the next actor. - if err := s.ensureEth0InPodNetns(ctx); err != nil { - return nil, fmt.Errorf("while recovering eth0 from prior failure: %w", err) - } - if err := s.moveEth0IntoInteriorNetns(ctx); err != nil { - return nil, fmt.Errorf("while moving eth0 into interior netns: %w", err) + // Networking (mirrors ateom-gvisor's veth model): build the per-activation + // veth into the interior netns and point kata at it; kata wires the guest + // to the stable actor address via its tap + TC mirror. + if err := s.setupActorNetwork(ctx); err != nil { + return nil, fmt.Errorf("while setting up actor network: %w", err) } defer func() { if retErr != nil { - if cleanupErr := s.ensureEth0InPodNetns(ctx); cleanupErr != nil { - slog.WarnContext(ctx, "Failed to roll back eth0 after Run failure", slog.Any("err", cleanupErr)) + if cleanupErr := s.cleanupActorNetwork(ctx); cleanupErr != nil { + slog.WarnContext(ctx, "Failed to clean up actor network after Run failure", slog.Any("err", cleanupErr)) } } }() @@ -462,9 +456,9 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec dTeardown := time.Since(tTeardown) delete(s.running, id) - // Return eth0 to the pod netns so the next actor can claim it (mirrors gVisor). - if err := s.returnEth0ToPodNetns(ctx); err != nil { - slog.WarnContext(ctx, "Failed to return eth0 to pod netns after checkpoint", slog.Any("err", err)) + // Tear down the per-activation actor network (mirrors gVisor). + if err := s.cleanupActorNetwork(ctx); err != nil { + slog.WarnContext(ctx, "Failed to clean up actor network after checkpoint", slog.Any("err", err)) } slog.InfoContext(ctx, "Actor checkpointed", slog.String("id", id), slog.Any("snapshot_files", snapshotFiles), @@ -589,19 +583,18 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore return nil, fmt.Errorf("while creating VM dir: %w", err) } - // 2. Networking: move the pod's eth0 into the interior netns and rebuild - // kata's tap + TC mirror there. The snapshot's virtio-net device is - // fd-backed, so CH requires fresh tap FDs on restore (net_fds). - if err := s.ensureEth0InPodNetns(ctx); err != nil { - return nil, fmt.Errorf("while recovering eth0 from prior failure: %w", err) - } - if err := s.moveEth0IntoInteriorNetns(ctx); err != nil { - return nil, fmt.Errorf("while moving eth0 into interior netns: %w", err) + // 2. Networking: rebuild the per-activation actor veth, then recreate + // kata's tap + TC mirror against it. The snapshot's virtio-net device is + // fd-backed, so CH requires fresh tap FDs on restore (net_fds). The guest's + // frozen network config (stable actor address, gateway with a fixed MAC) + // remains valid as-is — no in-guest reconfiguration. + if err := s.setupActorNetwork(ctx); err != nil { + return nil, fmt.Errorf("while setting up actor network: %w", err) } defer func() { if retErr != nil { - if cleanupErr := s.ensureEth0InPodNetns(ctx); cleanupErr != nil { - slog.WarnContext(ctx, "Failed to roll back eth0 after Restore failure", slog.Any("err", cleanupErr)) + if cleanupErr := s.cleanupActorNetwork(ctx); cleanupErr != nil { + slog.WarnContext(ctx, "Failed to clean up actor network after Restore failure", slog.Any("err", cleanupErr)) } } }() @@ -672,31 +665,6 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore return nil, fmt.Errorf("while resuming restored guest: %w", err) } - // 6. The guest's frozen network config carries the SOURCE pod's IP; rewrite - // it to this pod's eth0 address so ingress (router -> pod IP) reaches the - // guest. POC: drive the kata-agent debug console over vsock; the proper fix - // is the agent's UpdateInterface/UpdateRoutes RPCs. - // Retry: right after vm.resume the guest's vsock can refuse connections for - // a moment until the virtio-vsock device settles. - guestMAC := "" - if len(netDevs) > 0 { - guestMAC = netDevs[0].MAC - } - var netErr error - for attempt := 0; attempt < 10; attempt++ { - if netErr = s.reconfigureGuestNetwork(ctx, kata.VsockSocketPath(id), guestMAC); netErr == nil { - break - } - select { - case <-ctx.Done(): - attempt = 10 - case <-time.After(time.Second): - } - } - if netErr != nil { - slog.WarnContext(ctx, "Failed to update restored guest IP (ingress may not work)", slog.Any("err", netErr)) - } - s.running[id] = &runningActor{chCmd: chCmd, vfsdCmd: vfsdCmd, apiSocket: apiSocket} slog.InfoContext(ctx, "Actor restored", slog.String("id", id)) return &ateompb.RestoreWorkloadResponse{}, nil @@ -733,74 +701,6 @@ func rewriteSnapshotSocketPaths(snapshotDir, id string) error { return os.WriteFile(cfgPath, out, 0o600) } -// reconfigureGuestNetwork sets the restored guest's eth0 to THIS pod's IP -// config via the kata-agent's UpdateInterface/UpdateRoutes ttrpc RPCs over -// hybrid vsock (the same channel the kata shim uses, so it works on a restored -// VM with no shim). The snapshot froze the source pod's IP; on a flat pod -// subnet the routes stay valid, only the address must change. guestMAC -// identifies the device the way kata does (and avoids clearing the MAC). -func (s *AteomService) reconfigureGuestNetwork(ctx context.Context, vsockPath, guestMAC string) error { - var addr *SaveAddr - for i := range s.eth0LinkInfo.Addresses { - if v4 := s.eth0LinkInfo.Addresses[i].Addr.IP.To4(); v4 != nil { - addr = &s.eth0LinkInfo.Addresses[i] - break - } - } - if addr == nil { - return fmt.Errorf("no IPv4 address recorded for pod eth0") - } - var gw net.IP - for _, r := range s.eth0LinkInfo.Routes { - if r.Gateway != nil && r.Dst.IP.Equal(net.IPv4zero) { - gw = r.Gateway - break - } - } - - cctx, cancel := context.WithTimeout(ctx, 15*time.Second) - defer cancel() - agent, err := kata.DialAgent(cctx, vsockPath) - if err != nil { - return err - } - defer agent.Close() - - // The address is installed as /32 deliberately: a connected subnet route - // would make the guest ARP directly for same-subnet pod IPs, which only - // works on CNIs that proxy-ARP on the host veth (kind's ptp does, GKE does - // not — observed SYN-ACKs dying on unanswered ARP). With /32 everything - // routes via the gateway, which the node always answers for. - iface := &agentpb.Interface{ - Device: "eth0", - Name: "eth0", - Mtu: uint64(s.eth0LinkInfo.MTU), - HwAddr: guestMAC, - IPAddresses: []*agentpb.IPAddress{{ - Family: agentpb.IPFamily_v4, - Address: addr.Addr.IP.String(), - Mask: "32", - }}, - } - if _, err := agent.UpdateInterface(cctx, iface); err != nil { - return err - } - if gw != nil { - // Replace the guest routing table: gateway host route + default via it - // (the connected subnet route reappears with the address add). - routes := []*agentpb.Route{ - {Dest: gw.String() + "/32", Device: "eth0", Scope: uint32(netlink.SCOPE_LINK), Family: agentpb.IPFamily_v4}, - {Gateway: gw.String(), Device: "eth0", Family: agentpb.IPFamily_v4}, - } - if _, err := agent.UpdateRoutes(cctx, routes); err != nil { - return err - } - } - slog.InfoContext(ctx, "Restored guest network reconfigured", - slog.String("ip", addr.Addr.IP.String()+"/32"), slog.Any("gw", gw)) - return nil -} - // slogWriter adapts an io.Writer to slog at info level, for the kata shim's // start/delete diagnostics. type slogWriter struct{ ctx context.Context } diff --git a/cmd/ateom-cloud-hypervisor/service_integration_test.go b/cmd/ateom-cloud-hypervisor/service_integration_test.go index b67115517..a217a6507 100644 --- a/cmd/ateom-cloud-hypervisor/service_integration_test.go +++ b/cmd/ateom-cloud-hypervisor/service_integration_test.go @@ -72,7 +72,7 @@ func TestServiceE2E(t *testing.T) { writeMinimalGvisorStyleSpec(t, bundle) // no linux.resources -> exercises ensureKataCompatibleSpec drainBundleLog(t, bundle) // visibility into shim logs if anything fails - svc := NewService("testpod", shim, chBin, vfsdBin, "", "default", true, -1, &SaveLinkInfo{}) + svc := NewService("testpod", shim, chBin, vfsdBin, "", "default", true, -1) ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second) defer cancel()