Skip to content

Commit 5088d9c

Browse files
[CLD-568]: fix: migrate update node from CLD (#352)
Update Node in CLD requires a few other components. pkg/offchain/node_cfg.go -> engine/cld/offchain/node_key.go (separated out node key) pkg/offchain/node_cfg.go -> offchain/node/config.go (separated out NodeCfg struct) pkg/offchain/update_node.go -> engine/cld/offchain/update_node.go pkg/lib/p2pkey/peer_id.go -> offchain/internal/p2pkey/peer_id.go added more tests too JIRA: https://smartcontract-it.atlassian.net/browse/CLD-568
1 parent f93a6b2 commit 5088d9c

File tree

9 files changed

+1682
-27
lines changed

9 files changed

+1682
-27
lines changed

.changeset/sour-windows-begin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": minor
3+
---
4+
5+
fix: migrate Update Node from CLD

engine/cld/offchain/node_key.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package offchain
2+
3+
// NodeKey is the key type to use to find a node
4+
type NodeKey string
5+
6+
const (
7+
NodeKey_ID NodeKey = "id"
8+
NodeKey_CSAKey NodeKey = "csa_key"
9+
NodeKey_Name NodeKey = "name"
10+
NodeKey_Label NodeKey = "label"
11+
)

engine/cld/offchain/propose_job_test.go

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -430,34 +430,40 @@ func TestProposeJob_WithComplexSelectors(t *testing.T) {
430430
Nodes: nodes,
431431
}
432432

433-
// Expected selectors with multiple node labels
434-
expectedFilter := &nodev1.ListNodesRequest_Filter{
435-
Enabled: 1,
436-
Selectors: []*ptypes.Selector{
437-
{
438-
Key: "product",
439-
Op: ptypes.SelectorOp_EQ,
440-
Value: pointer.To("keystone"),
441-
},
442-
{
443-
Key: "environment",
444-
Op: ptypes.SelectorOp_EQ,
445-
Value: pointer.To("staging"),
446-
},
447-
{
448-
Key: "region",
449-
Op: ptypes.SelectorOp_EQ,
450-
Value: pointer.To("us-west-2"),
451-
},
452-
{
453-
Key: "type",
454-
Op: ptypes.SelectorOp_EQ,
455-
Value: pointer.To("plugin"),
456-
},
457-
},
458-
}
433+
// Use mock.MatchedBy to handle selector ordering since maps don't guarantee iteration order
434+
mockClient.MockNodeServiceClient.On("ListNodes", ctx, mock.MatchedBy(func(req *nodev1.ListNodesRequest) bool {
435+
if req.Filter == nil || req.Filter.Enabled != 1 {
436+
return false
437+
}
459438

460-
mockClient.MockNodeServiceClient.On("ListNodes", ctx, &nodev1.ListNodesRequest{Filter: expectedFilter}).Return(listNodesResponse, nil)
439+
// Convert selectors to map for comparison
440+
selectorMap := make(map[string]string)
441+
for _, selector := range req.Filter.Selectors {
442+
if selector.Value != nil {
443+
selectorMap[selector.Key] = *selector.Value
444+
}
445+
}
446+
447+
// Check expected selectors
448+
expectedSelectors := map[string]string{
449+
"product": "keystone",
450+
"environment": "staging",
451+
"region": "us-west-2",
452+
"type": "plugin",
453+
}
454+
455+
if len(selectorMap) != len(expectedSelectors) {
456+
return false
457+
}
458+
459+
for key, expectedValue := range expectedSelectors {
460+
if actualValue, exists := selectorMap[key]; !exists || actualValue != expectedValue {
461+
return false
462+
}
463+
}
464+
465+
return true
466+
})).Return(listNodesResponse, nil)
461467

462468
proposeJobResponse := &jobv1.ProposeJobResponse{
463469
Proposal: &jobv1.Proposal{

engine/cld/offchain/update_node.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package offchain
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
nodev1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node"
9+
10+
cldf_offchain "github.com/smartcontractkit/chainlink-deployments-framework/offchain"
11+
"github.com/smartcontractkit/chainlink-deployments-framework/offchain/node"
12+
13+
"github.com/smartcontractkit/chainlink-protos/job-distributor/v1/shared/ptypes"
14+
)
15+
16+
// UpdateNodeRequest is the request to update a node using JD
17+
type UpdateNodeRequest struct {
18+
// Cfg is the configuration for the used to update node
19+
Cfg node.NodeCfg
20+
// keyCfg is the configuration for how to find the node to update
21+
keyCfg nodeKeyCfg
22+
}
23+
24+
// NodeFinderCfg is the configuration for how to find the node to update
25+
type NodeFinderCfg struct {
26+
KeyType NodeKey // type of key to search for
27+
LabelName *string // name of label to search for when using NodeKey_Label
28+
}
29+
30+
func (c NodeFinderCfg) Validate() error {
31+
if c.KeyType == NodeKey_Label && c.LabelName == nil {
32+
return errors.New("label name is required for label search")
33+
}
34+
35+
return nil
36+
}
37+
38+
// NewUpdateNodeRequest creates a new UpdateNodeRequest
39+
func NewUpdateNodeRequest(cfg node.NodeCfg, f NodeFinderCfg) (*UpdateNodeRequest, error) {
40+
if err := f.Validate(); err != nil {
41+
return nil, fmt.Errorf("invalid node finder config: %w", err)
42+
}
43+
kc, err := newNodeKeyCfg(cfg, f)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
return &UpdateNodeRequest{
49+
Cfg: cfg,
50+
keyCfg: kc,
51+
}, nil
52+
}
53+
54+
// Labels returns the labels for the node, containing the p2p_id, nop, admin_addr and all tags.
55+
func (r *UpdateNodeRequest) Labels() []*ptypes.Label {
56+
raw := r.Cfg.Labels()
57+
58+
out := make([]*ptypes.Label, 0, len(raw))
59+
for k, v := range raw {
60+
out = append(out, &ptypes.Label{
61+
Key: k,
62+
Value: &v,
63+
})
64+
}
65+
66+
return out
67+
}
68+
69+
// NodeKeyCriteria returns the node key criteria
70+
func (r *UpdateNodeRequest) NodeKeyCriteria() string {
71+
return r.keyCfg.String()
72+
}
73+
74+
// UpdateNodesRequest is the request to update multiple nodes using JD
75+
type UpdateNodesRequest struct {
76+
Requests []*UpdateNodeRequest
77+
}
78+
79+
// UpdateNodes updates the nodes with the given configurations.
80+
func UpdateNodes(ctx context.Context, client cldf_offchain.Client, req UpdateNodesRequest) error {
81+
if len(req.Requests) == 0 {
82+
return nil
83+
}
84+
resp, err := client.ListNodes(ctx, &nodev1.ListNodesRequest{})
85+
if err != nil {
86+
return err
87+
}
88+
for _, r := range req.Requests {
89+
node, err := getNode(resp, r.keyCfg.keyType, r.keyCfg.value, r.keyCfg.labelKey)
90+
if err != nil {
91+
return fmt.Errorf("failed to get node %s with value %s (label=%v): %w", r.
92+
keyCfg.keyType, r.keyCfg.value, r.keyCfg.labelKey, err)
93+
}
94+
_, err = client.UpdateNode(ctx, &nodev1.UpdateNodeRequest{
95+
Id: node.GetId(),
96+
Name: r.Cfg.Name,
97+
PublicKey: r.Cfg.CSAKey,
98+
Labels: r.Labels(),
99+
})
100+
if err != nil {
101+
return fmt.Errorf("failed to update node %s name %s csakey %s: %w", node.GetId(), r.Cfg.Name, r.Cfg.CSAKey, err)
102+
}
103+
}
104+
105+
return nil
106+
}
107+
108+
// getNode gets a node from the list of nodes based on the key type and key value.
109+
// the key is only used for label searches and it is required for label searches.
110+
func getNode(resp *nodev1.ListNodesResponse, keyType NodeKey, value string, labelKey *string) (*nodev1.Node, error) {
111+
switch keyType {
112+
case NodeKey_ID:
113+
return getNodeByID(resp, value)
114+
case NodeKey_CSAKey:
115+
return getNodeByCSAKey(resp, value)
116+
case NodeKey_Name:
117+
return getNodeByName(resp, value)
118+
case NodeKey_Label:
119+
if labelKey == nil {
120+
return nil, errors.New("no key provided for label search")
121+
}
122+
123+
return getNodeByLabel(resp, *labelKey, value)
124+
default:
125+
return nil, fmt.Errorf("unknown key type %s", keyType)
126+
}
127+
}
128+
129+
func getNodeByID(resp *nodev1.ListNodesResponse, id string) (*nodev1.Node, error) {
130+
for _, node := range resp.GetNodes() {
131+
if node.GetId() == id {
132+
return node, nil
133+
}
134+
}
135+
136+
return nil, fmt.Errorf("no node with id %s found", id)
137+
}
138+
139+
func getNodeByCSAKey(resp *nodev1.ListNodesResponse, csaKey string) (*nodev1.Node, error) {
140+
for _, node := range resp.GetNodes() {
141+
if node.GetPublicKey() == csaKey {
142+
return node, nil
143+
}
144+
}
145+
146+
return nil, fmt.Errorf("no node with csa key %s found", csaKey)
147+
}
148+
149+
func getNodeByName(resp *nodev1.ListNodesResponse, name string) (*nodev1.Node, error) {
150+
for _, node := range resp.GetNodes() {
151+
if node.GetName() == name {
152+
return node, nil
153+
}
154+
}
155+
156+
return nil, fmt.Errorf("no node with name %s found", name)
157+
}
158+
159+
func getNodeByLabel(resp *nodev1.ListNodesResponse, key, value string) (*nodev1.Node, error) {
160+
for _, node := range resp.GetNodes() {
161+
for _, label := range node.GetLabels() {
162+
if label.GetKey() == key && label.GetValue() == value {
163+
return node, nil
164+
}
165+
}
166+
}
167+
168+
return nil, fmt.Errorf("no node with label %s=%s found", key, value)
169+
}
170+
171+
type nodeKeyCfg struct {
172+
keyType NodeKey // type of key to search for
173+
value string // value to search for
174+
labelKey *string // key to search for in labels when using NodeKey_Label
175+
}
176+
177+
func (c nodeKeyCfg) String() string {
178+
v := c.value
179+
if c.labelKey != nil {
180+
v = fmt.Sprintf("%s=%s", *c.labelKey, c.value)
181+
}
182+
183+
return fmt.Sprintf("key-type=%s, value=%s", c.keyType, v)
184+
}
185+
186+
func newNodeKeyCfg(n node.NodeCfg, c NodeFinderCfg) (nodeKeyCfg, error) {
187+
out := nodeKeyCfg{
188+
keyType: c.KeyType,
189+
}
190+
switch c.KeyType {
191+
case NodeKey_CSAKey:
192+
out.value = n.CSAKey
193+
case NodeKey_Name:
194+
out.value = n.Name
195+
case NodeKey_Label:
196+
out.value = n.Labels()[*c.LabelName]
197+
out.labelKey = c.LabelName
198+
case NodeKey_ID:
199+
return nodeKeyCfg{}, errors.New("id key type is not supported")
200+
default:
201+
return nodeKeyCfg{}, fmt.Errorf("unknown key type %s", c.KeyType)
202+
}
203+
204+
return out, nil
205+
}

0 commit comments

Comments
 (0)