-
Notifications
You must be signed in to change notification settings - Fork 649
/
factory.go
142 lines (123 loc) · 3.32 KB
/
factory.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright (C) 2019-2021, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcchainvm
import (
"errors"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/utils/subprocess"
"github.com/ava-labs/avalanchego/vms"
)
var errWrongVM = errors.New("wrong vm type")
type Factory struct {
Path string
}
func (f *Factory) New(ctx *snow.Context) (interface{}, error) {
config := &plugin.ClientConfig{
HandshakeConfig: Handshake,
Plugins: PluginMap,
Cmd: subprocess.New(f.Path),
AllowedProtocols: []plugin.Protocol{
plugin.ProtocolGRPC,
},
// We kill this client by calling kill() when the chain running this VM
// shuts down. However, there are some cases where the VM's Shutdown
// method is not called. Namely, if:
// 1) The node shuts down after the client is created but before the
// chain is registered with the message router.
// 2) The chain doesn't handle a shutdown message before the node times
// out on the chain's shutdown and dies, leaving the shutdown message
// unhandled.
// We set managed to true so that we can call plugin.CleanupClients on
// node shutdown to ensure every plugin subprocess is killed.
Managed: true,
}
if ctx != nil {
log.SetOutput(ctx.Log)
config.Stderr = ctx.Log
config.Logger = hclog.New(&hclog.LoggerOptions{
Output: ctx.Log,
Level: hclog.Info,
})
} else {
log.SetOutput(ioutil.Discard)
config.Stderr = ioutil.Discard
config.Logger = hclog.New(&hclog.LoggerOptions{
Output: ioutil.Discard,
})
}
client := plugin.NewClient(config)
rpcClient, err := client.Client()
if err != nil {
client.Kill()
return nil, err
}
raw, err := rpcClient.Dispense("vm")
if err != nil {
client.Kill()
return nil, err
}
vm, ok := raw.(*VMClient)
if !ok {
client.Kill()
return nil, errWrongVM
}
vm.SetProcess(client)
vm.ctx = ctx
return vm, nil
}
// RegisterPlugins iterates over a given plugin dir and registers rpcchain VMs
// for each of the discovered plugins.
func RegisterPlugins(pluginDir string, manager vms.Manager) error {
files, err := ioutil.ReadDir(pluginDir)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() {
continue
}
nameWithExtension := file.Name()
// Strip any extension from the file. This is to support windows .exe
// files.
name := nameWithExtension[:len(nameWithExtension)-len(filepath.Ext(nameWithExtension))]
// Skip hidden files.
if len(name) == 0 {
continue
}
vmID, err := manager.Lookup(name)
if err != nil {
// there is no alias with plugin name, try to use full vmID.
vmID, err = ids.FromString(name)
if err != nil {
return fmt.Errorf("invalid vmID %s", name)
}
}
_, err = manager.GetFactory(vmID)
if err == nil {
// If we already have the VM registered, we shouldn't attempt to
// register it again.
continue
}
// If the error isn't "not found", then we should report the error.
if !errors.Is(err, vms.ErrNotFound) {
return err
}
err = manager.RegisterFactory(
vmID,
&Factory{
Path: filepath.Join(pluginDir, file.Name()),
},
)
if err != nil {
return err
}
}
return nil
}