Skip to content

Commit 90a3f0d

Browse files
authored
[config] Add Orderer endpoint (#5)
#### Type of change - Improvement #### Description - Add Orderer endpoint to profile - Allow giving Orderer endpoint as pure YAML - Allow reuse of parsing code by other components - Add test - Update sample - Export reusable viper decoding hooks - Fix lint issues - Upgrade to `yaml.v3` Signed-off-by: Liran Funaro <[email protected]>
1 parent c38a846 commit 90a3f0d

File tree

18 files changed

+552
-163
lines changed

18 files changed

+552
-163
lines changed

api/types/orderer_endpoint.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package types
8+
9+
import (
10+
"encoding/json"
11+
"errors"
12+
"fmt"
13+
"net"
14+
"slices"
15+
"strconv"
16+
"strings"
17+
18+
"gopkg.in/yaml.v3"
19+
)
20+
21+
type (
22+
// OrdererEndpoint defines an orderer party's endpoint.
23+
OrdererEndpoint struct {
24+
Host string `mapstructure:"host" json:"host,omitempty" yaml:"host,omitempty"`
25+
Port int `mapstructure:"port" json:"port,omitempty" yaml:"port,omitempty"`
26+
// ID is the party ID.
27+
ID uint32 `mapstructure:"id" json:"id,omitempty" yaml:"id,omitempty"`
28+
MspID string `mapstructure:"msp-id" json:"msp-id,omitempty" yaml:"msp-id,omitempty"`
29+
// API should be broadcast and/or deliver. Empty value means all API is supported.
30+
API []string `mapstructure:"api" json:"api,omitempty" yaml:"api,omitempty"`
31+
}
32+
)
33+
34+
const (
35+
// Broadcast support by endpoint.
36+
Broadcast = "broadcast"
37+
// Deliver support by endpoint.
38+
Deliver = "deliver"
39+
// NoID indicates that a party ID is not specified (default).
40+
// This allows backward compatibility with orderers that doesn't support this syntax.
41+
NoID = uint32(0x100000)
42+
)
43+
44+
// ErrInvalidEndpoint orderer endpoints error.
45+
var ErrInvalidEndpoint = errors.New("invalid endpoint")
46+
47+
// Address returns a string representation of the endpoint's address.
48+
func (e *OrdererEndpoint) Address() string {
49+
return net.JoinHostPort(e.Host, strconv.Itoa(e.Port))
50+
}
51+
52+
// String returns a deterministic representation of the endpoint.
53+
func (e *OrdererEndpoint) String() string {
54+
var output strings.Builder
55+
isFirst := true
56+
if e.ID < NoID {
57+
output.WriteString("id=")
58+
output.WriteString(strconv.FormatUint(uint64(e.ID), 10))
59+
isFirst = false
60+
}
61+
if len(e.MspID) > 0 {
62+
if !isFirst {
63+
output.WriteRune(',')
64+
}
65+
output.WriteString("msp-id=")
66+
output.WriteString(e.MspID)
67+
isFirst = false
68+
}
69+
for _, api := range e.API {
70+
if !isFirst {
71+
output.WriteRune(',')
72+
}
73+
output.WriteString(api)
74+
isFirst = false
75+
}
76+
if len(e.Host) > 0 || e.Port > 0 {
77+
if !isFirst {
78+
output.WriteRune(',')
79+
}
80+
output.WriteString(e.Address())
81+
}
82+
return output.String()
83+
}
84+
85+
// SupportsAPI returns true if this endpoint supports API.
86+
// It also returns true if no APIs are specified, as we cannot know.
87+
func (e *OrdererEndpoint) SupportsAPI(api string) bool {
88+
return len(e.API) == 0 || slices.Contains(e.API, api)
89+
}
90+
91+
// ParseOrdererEndpoint parses a string according to the following schema order (the first that succeeds).
92+
// Schema 1: YAML.
93+
// Schema 2: JSON.
94+
// Schema 3: [id=ID,][msp-id=MspID,][broadcast,][deliver,][host=Host,][port=Port,][Host:Port].
95+
func ParseOrdererEndpoint(valueRaw string) (*OrdererEndpoint, error) {
96+
ret := &OrdererEndpoint{ID: NoID}
97+
if len(valueRaw) == 0 {
98+
return ret, nil
99+
}
100+
if err := yaml.Unmarshal([]byte(valueRaw), ret); err == nil {
101+
return ret, nil
102+
}
103+
if err := json.Unmarshal([]byte(valueRaw), ret); err == nil {
104+
return ret, nil
105+
}
106+
err := unmarshalOrdererEndpoint(valueRaw, ret)
107+
return ret, err
108+
}
109+
110+
func unmarshalOrdererEndpoint(valueRaw string, out *OrdererEndpoint) error {
111+
metaParts := strings.Split(valueRaw, ",")
112+
for _, item := range metaParts {
113+
item = strings.TrimSpace(item)
114+
equalIdx := strings.Index(item, "=")
115+
colonIdx := strings.Index(item, ":")
116+
var err error
117+
switch {
118+
case item == Broadcast || item == Deliver:
119+
out.API = append(out.API, item)
120+
case equalIdx >= 0:
121+
key, value := strings.TrimSpace(item[:equalIdx]), strings.TrimSpace(item[equalIdx+1:])
122+
switch key {
123+
case "msp-id":
124+
out.MspID = value
125+
case "host":
126+
out.Host = value
127+
case "id":
128+
err = out.setID(value)
129+
case "port":
130+
err = out.setPort(value)
131+
default:
132+
return fmt.Errorf("invalid key '%s' for item '%s': %w", key, item, ErrInvalidEndpoint)
133+
}
134+
case colonIdx >= 0:
135+
var port string
136+
out.Host, port, err = net.SplitHostPort(strings.TrimSpace(item))
137+
if err != nil {
138+
return fmt.Errorf("invalid host/port '%s': %w", item, err)
139+
}
140+
err = out.setPort(strings.TrimSpace(port))
141+
default:
142+
return fmt.Errorf("invalid item '%s': %w", item, ErrInvalidEndpoint)
143+
}
144+
if err != nil {
145+
return err
146+
}
147+
}
148+
return nil
149+
}
150+
151+
func (e *OrdererEndpoint) setPort(portStr string) error {
152+
port, err := strconv.ParseInt(portStr, 10, 32)
153+
if err != nil {
154+
return fmt.Errorf("failed to parse port: %w", err)
155+
}
156+
e.Port = int(port)
157+
return nil
158+
}
159+
160+
func (e *OrdererEndpoint) setID(idStr string) error {
161+
id, err := strconv.ParseUint(idStr, 10, 32)
162+
if err != nil {
163+
return fmt.Errorf("invalid id value: %w", err)
164+
}
165+
e.ID = uint32(id)
166+
return nil
167+
}

api/types/orderer_endpoint_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package types
8+
9+
import (
10+
"encoding/json"
11+
"testing"
12+
13+
"github.com/stretchr/testify/require"
14+
"gopkg.in/yaml.v3"
15+
)
16+
17+
func TestReadWrite(t *testing.T) {
18+
t.Parallel()
19+
valSchema := "id=5,msp-id=org,broadcast,deliver,localhost:5050"
20+
valJSON := `{"id":5,"msp-id":"org","api":["broadcast","deliver"],"host":"localhost","port":5050}`
21+
valYAML := `
22+
id: 5
23+
msp-id: org
24+
api:
25+
- broadcast
26+
- deliver
27+
host: localhost
28+
port: 5050
29+
`
30+
expected := &OrdererEndpoint{
31+
ID: 5,
32+
MspID: "org",
33+
API: []string{"broadcast", "deliver"},
34+
Host: "localhost",
35+
Port: 5050,
36+
}
37+
require.Equal(t, "localhost:5050", expected.Address())
38+
require.Equal(t, valSchema, expected.String())
39+
40+
valJSONRaw, err := json.Marshal(expected)
41+
require.NoError(t, err)
42+
require.JSONEq(t, valJSON, string(valJSONRaw))
43+
44+
valYamlRaw, err := yaml.Marshal(expected)
45+
require.NoError(t, err)
46+
require.YAMLEq(t, valYAML, string(valYamlRaw))
47+
48+
e, err := ParseOrdererEndpoint(valSchema)
49+
require.NoError(t, err)
50+
require.Equal(t, expected, e)
51+
52+
e, err = ParseOrdererEndpoint(valJSON)
53+
require.NoError(t, err)
54+
require.Equal(t, expected, e)
55+
56+
e, err = ParseOrdererEndpoint(valYAML)
57+
require.NoError(t, err)
58+
require.Equal(t, expected, e)
59+
60+
valJSONNoID := `{"msp-id":"org","api":["broadcast","deliver"],"host":"localhost","port":5050}`
61+
e, err = ParseOrdererEndpoint(valJSONNoID)
62+
require.NoError(t, err)
63+
require.Equal(t, &OrdererEndpoint{
64+
ID: NoID,
65+
MspID: "org",
66+
API: []string{"broadcast", "deliver"},
67+
Host: "localhost",
68+
Port: 5050,
69+
}, e)
70+
}

cmd/common/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"os"
1111

1212
"github.com/pkg/errors"
13-
"gopkg.in/yaml.v2"
13+
"gopkg.in/yaml.v3"
1414

1515
"github.com/hyperledger/fabric-x-common/cmd/common/comm"
1616
"github.com/hyperledger/fabric-x-common/cmd/common/signer"

cmd/cryptogen/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"github.com/hyperledger/fabric-x-common/internaltools/cryptogen/msp"
2020

2121
"github.com/alecthomas/kingpin/v2"
22-
"gopkg.in/yaml.v2"
22+
"gopkg.in/yaml.v3"
2323
)
2424

2525
const (

cmd/cryptogen/main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"testing"
1010

1111
"github.com/stretchr/testify/require"
12-
yaml "gopkg.in/yaml.v2"
12+
"gopkg.in/yaml.v3"
1313
)
1414

1515
func TestDefaultConfigParsing(t *testing.T) {

common/channelconfig/realconfig_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/hyperledger/fabric-protos-go-apiv2/common"
1414
"github.com/stretchr/testify/require"
1515

16+
"github.com/hyperledger/fabric-x-common/api/types"
1617
"github.com/hyperledger/fabric-x-common/common/channelconfig"
1718
"github.com/hyperledger/fabric-x-common/core/config/configtest"
1819
"github.com/hyperledger/fabric-x-common/internaltools/configtxgen/encoder"
@@ -57,7 +58,7 @@ func TestOrgSpecificOrdererEndpoints(t *testing.T) {
5758
require.Nil(t, cg)
5859
require.EqualError(t, err, "could not create orderer group: failed to create orderer org: orderer endpoints for organization SampleOrg are missing and must be configured when capability V3_0 is enabled")
5960

60-
conf.Orderer.Organizations[0].OrdererEndpoints = []string{"127.0.0.1:7050"}
61+
conf.Orderer.Organizations[0].OrdererEndpoints = []*types.OrdererEndpoint{{Host: "127.0.0.1", Port: 7050}}
6162
cg, err = encoder.NewChannelGroup(conf)
6263
require.NoError(t, err)
6364

common/configtx/test/helper.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,12 @@ func MakeGenesisBlockFromMSPs(channelID string, appMSPConf, ordererMSPConf *mspp
6767
ModPolicy: channelconfig.AdminsPolicyKey,
6868
}
6969

70+
endpoints := profile.Orderer.Organizations[0].OrdererEndpoints
7071
ordererOrgProtos := &cb.OrdererAddresses{
71-
Addresses: profile.Orderer.Organizations[0].OrdererEndpoints,
72+
Addresses: make([]string, len(endpoints)),
73+
}
74+
for i, e := range endpoints {
75+
ordererOrgProtos.Addresses[i] = e.String()
7276
}
7377
ordererOrg.Values[channelconfig.EndpointsKey] = &cb.ConfigValue{
7478
Value: protoutil.MarshalOrPanic(ordererOrgProtos),

0 commit comments

Comments
 (0)