api/services/instrospection: add PluginInfo

The new `PlunginInfo()` call can be used for instrospecting the details
of the runtime plugin.

```console
$ ctr plugins inspect-runtime --runtime=io.containerd.runc.v2 --runc-binary=runc
{
    "Name": "io.containerd.runc.v2",
    "Version": {
        "Version": "v2.0.0-beta.0-XX-gXXXXXXXXX.m",
        "Revision": "v2.0.0-beta.0-XX-gXXXXXXXXX.m"
    },
    "Options": {
        "binary_name": "runc"
    },
    "Features": {
        "ociVersionMin": "1.0.0",
        "ociVersionMax": "1.1.0-rc.2",
        ...,
    },
    "Annotations": null
}
```

The shim binary has to support `-info` flag, see `runtime/v2/README.md`

Replaces PR 8509 (`api/services/task: add RuntimeInfo()`)

Co-authored-by: Derek McGowan <derek@mcg.dev>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
Akihiro Suda
2023-05-11 18:17:13 +09:00
parent 9dbb7615b6
commit 22d586e515
24 changed files with 1423 additions and 204 deletions

View File

@@ -29,6 +29,7 @@ import (
type Service interface {
Plugins(context.Context, []string) (*api.PluginsResponse, error)
Server(context.Context, *ptypes.Empty) (*api.ServerResponse, error)
PluginInfo(ctx context.Context, in *api.PluginInfoRequest) (*api.PluginInfoResponse, error)
}
type introspectionRemote struct {
@@ -64,3 +65,13 @@ func (i *introspectionRemote) Server(ctx context.Context, in *ptypes.Empty) (*ap
return resp, nil
}
func (i *introspectionRemote) PluginInfo(ctx context.Context, in *api.PluginInfoRequest) (*api.PluginInfoResponse, error) {
resp, err := i.client.PluginInfo(ctx, in)
if err != nil {
return nil, errdefs.FromGRPC(err)
}
return resp, nil
}

View File

@@ -19,6 +19,7 @@ package introspection
import (
context "context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
@@ -41,6 +42,7 @@ import (
ptypes "github.com/containerd/containerd/v2/protobuf/types"
"github.com/containerd/plugin"
"github.com/containerd/plugin/registry"
"github.com/containerd/typeurl/v2"
)
func init() {
@@ -209,47 +211,51 @@ func adaptPlugin(o interface{}) filters.Adaptor {
})
}
func pluginsToPB(plugins []*plugin.Plugin) []*api.Plugin {
var pluginsPB []*api.Plugin
for _, p := range plugins {
var requires []string
for _, r := range p.Registration.Requires {
requires = append(requires, r.String())
}
func pluginToPB(p *plugin.Plugin) *api.Plugin {
var requires []string
for _, r := range p.Registration.Requires {
requires = append(requires, r.String())
}
var initErr *rpc.Status
if err := p.Err(); err != nil {
st, ok := status.FromError(errdefs.ToGRPC(err))
if ok {
var details []*ptypes.Any
for _, d := range st.Proto().Details {
details = append(details, &ptypes.Any{
TypeUrl: d.TypeUrl,
Value: d.Value,
})
}
initErr = &rpc.Status{
Code: int32(st.Code()),
Message: st.Message(),
Details: details,
}
} else {
initErr = &rpc.Status{
Code: int32(code.Code_UNKNOWN),
Message: err.Error(),
}
var initErr *rpc.Status
if err := p.Err(); err != nil {
st, ok := status.FromError(errdefs.ToGRPC(err))
if ok {
var details []*ptypes.Any
for _, d := range st.Proto().Details {
details = append(details, &ptypes.Any{
TypeUrl: d.TypeUrl,
Value: d.Value,
})
}
initErr = &rpc.Status{
Code: int32(st.Code()),
Message: st.Message(),
Details: details,
}
} else {
initErr = &rpc.Status{
Code: int32(code.Code_UNKNOWN),
Message: err.Error(),
}
}
}
pluginsPB = append(pluginsPB, &api.Plugin{
Type: p.Registration.Type.String(),
ID: p.Registration.ID,
Requires: requires,
Platforms: types.OCIPlatformToProto(p.Meta.Platforms),
Capabilities: p.Meta.Capabilities,
Exports: p.Meta.Exports,
InitErr: initErr,
})
return &api.Plugin{
Type: p.Registration.Type.String(),
ID: p.Registration.ID,
Requires: requires,
Platforms: types.OCIPlatformToProto(p.Meta.Platforms),
Capabilities: p.Meta.Capabilities,
Exports: p.Meta.Exports,
InitErr: initErr,
}
}
func pluginsToPB(plugins []*plugin.Plugin) []*api.Plugin {
pluginsPB := make([]*api.Plugin, 0, len(plugins))
for _, p := range plugins {
pluginsPB = append(pluginsPB, pluginToPB(p))
}
return pluginsPB
@@ -267,3 +273,57 @@ func warningsPB(ctx context.Context, warnings []warning.Warning) []*api.Deprecat
}
return pb
}
type pluginInfoProvider interface {
PluginInfo(context.Context, interface{}) (interface{}, error)
}
func (l *Local) PluginInfo(ctx context.Context, req *api.PluginInfoRequest, _ ...grpc.CallOption) (*api.PluginInfoResponse, error) {
p := l.plugins.Get(plugin.Type(req.Type), req.ID)
if p == nil {
err := fmt.Errorf("plugin %s.%s not found: %w", req.Type, req.ID, errdefs.ErrNotFound)
return nil, errdefs.ToGRPC(err)
}
resp := &api.PluginInfoResponse{
Plugin: pluginToPB(p),
}
// Request additional info from plugin instance
if req.Options != nil {
if p.Err() != nil {
err := fmt.Errorf("cannot get extra info, plugin not successfully loaded: %w", errdefs.ErrFailedPrecondition)
return resp, errdefs.ToGRPC(err)
}
inst, err := p.Instance()
if err != nil {
err := fmt.Errorf("failed to get plugin instance: %w", errdefs.ErrFailedPrecondition)
return resp, errdefs.ToGRPC(err)
}
pi, ok := inst.(pluginInfoProvider)
if !ok {
err := fmt.Errorf("plugin does not provided extra information: %w", errdefs.ErrNotImplemented)
return resp, errdefs.ToGRPC(err)
}
options, err := typeurl.UnmarshalAny(req.Options)
if err != nil {
err = fmt.Errorf("failed to unmarshal plugin info Options: %w", err)
return resp, errdefs.ToGRPC(err)
}
info, err := pi.PluginInfo(ctx, options)
if err != nil {
return resp, errdefs.ToGRPC(err)
}
ai, err := typeurl.MarshalAny(info)
if err != nil {
err = fmt.Errorf("failed to marshal plugin info: %w", err)
return resp, errdefs.ToGRPC(err)
}
resp.Extra = &ptypes.Any{
TypeUrl: ai.GetTypeUrl(),
Value: ai.GetValue(),
}
}
return resp, nil
}

View File

@@ -72,3 +72,7 @@ func (s *server) Plugins(ctx context.Context, req *api.PluginsRequest) (*api.Plu
func (s *server) Server(ctx context.Context, empty *ptypes.Empty) (*api.ServerResponse, error) {
return s.local.Server(ctx, empty)
}
func (s *server) PluginInfo(ctx context.Context, req *api.PluginInfoRequest) (*api.PluginInfoResponse, error) {
return s.local.PluginInfo(ctx, req)
}