240 lines
7.8 KiB
Go
240 lines
7.8 KiB
Go
/*
|
|
Copyright IBM Corp All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package channelparticipation
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hyperledger/fabric-protos-go/common"
|
|
"github.com/hyperledger/fabric/integration/nwo"
|
|
ginkgo "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
"github.com/onsi/gomega/gstruct"
|
|
"github.com/onsi/gomega/types"
|
|
ginkgomon "github.com/tedsuo/ifrit/ginkgomon_v2"
|
|
)
|
|
|
|
func Join(n *nwo.Network, o *nwo.Orderer, channel string, block *common.Block, expectedChannelInfo ChannelInfo) {
|
|
blockBytes, err := proto.Marshal(block)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
protocol := "http"
|
|
if n.TLSEnabled {
|
|
protocol = "https"
|
|
}
|
|
url := fmt.Sprintf("%s://127.0.0.1:%d/participation/v1/channels", protocol, n.OrdererPort(o, nwo.AdminPort))
|
|
req := GenerateJoinRequest(url, channel, blockBytes)
|
|
authClient, unauthClient := nwo.OrdererOperationalClients(n, o)
|
|
|
|
client := unauthClient
|
|
if n.TLSEnabled {
|
|
client = authClient
|
|
}
|
|
body := doBody(client, req)
|
|
c := &ChannelInfo{}
|
|
err = json.Unmarshal(body, c)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(*c).To(Equal(expectedChannelInfo))
|
|
}
|
|
|
|
func GenerateJoinRequest(url, channel string, blockBytes []byte) *http.Request {
|
|
joinBody := new(bytes.Buffer)
|
|
writer := multipart.NewWriter(joinBody)
|
|
part, err := writer.CreateFormFile("config-block", fmt.Sprintf("%s.block", channel))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
part.Write(blockBytes)
|
|
err = writer.Close()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, joinBody)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
return req
|
|
}
|
|
|
|
func doBody(client *http.Client, req *http.Request) []byte {
|
|
resp, err := client.Do(req)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.StatusCode).To(Equal(http.StatusCreated))
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
resp.Body.Close()
|
|
|
|
return bodyBytes
|
|
}
|
|
|
|
type ChannelList struct {
|
|
// Deprecated: system channel no longer supported
|
|
SystemChannel *ChannelInfoShort `json:"systemChannel"`
|
|
Channels []ChannelInfoShort `json:"channels"`
|
|
}
|
|
|
|
type ChannelInfoShort struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
func List(n *nwo.Network, o *nwo.Orderer) ChannelList {
|
|
authClient, _ := nwo.OrdererOperationalClients(n, o)
|
|
|
|
protocol := "http"
|
|
if n.TLSEnabled {
|
|
protocol = "https"
|
|
}
|
|
listChannelsURL := fmt.Sprintf("%s://127.0.0.1:%d/participation/v1/channels", protocol, n.OrdererPort(o, nwo.AdminPort))
|
|
|
|
body := getBody(authClient, listChannelsURL)()
|
|
list := &ChannelList{}
|
|
err := json.Unmarshal([]byte(body), list)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
return *list
|
|
}
|
|
|
|
func getBody(client *http.Client, url string) func() string {
|
|
return func() string {
|
|
resp, err := client.Get(url)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
resp.Body.Close()
|
|
return string(bodyBytes)
|
|
}
|
|
}
|
|
|
|
type ChannelInfo struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
Status string `json:"status"`
|
|
ConsensusRelation string `json:"consensusRelation"`
|
|
Height uint64 `json:"height"`
|
|
}
|
|
|
|
func ListOne(n *nwo.Network, o *nwo.Orderer, channel string) ChannelInfo {
|
|
authClient, _ := nwo.OrdererOperationalClients(n, o)
|
|
|
|
protocol := "http"
|
|
if n.TLSEnabled {
|
|
protocol = "https"
|
|
}
|
|
listChannelURL := fmt.Sprintf("%s://127.0.0.1:%d/participation/v1/channels/%s", protocol, n.OrdererPort(o, nwo.AdminPort), channel)
|
|
|
|
body := getBody(authClient, listChannelURL)()
|
|
c := &ChannelInfo{}
|
|
err := json.Unmarshal([]byte(body), c)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return *c
|
|
}
|
|
|
|
func Remove(n *nwo.Network, o *nwo.Orderer, channel string) {
|
|
authClient, _ := nwo.OrdererOperationalClients(n, o)
|
|
|
|
protocol := "http"
|
|
if n.TLSEnabled {
|
|
protocol = "https"
|
|
}
|
|
url := fmt.Sprintf("%s://127.0.0.1:%d/participation/v1/channels/%s", protocol, n.OrdererPort(o, nwo.AdminPort), channel)
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
resp, err := authClient.Do(req)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(resp.StatusCode).To(Equal(http.StatusNoContent))
|
|
}
|
|
|
|
func ChannelListMatcher(list ChannelList, expectedChannels []string) {
|
|
Expect(list).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
|
|
"Channels": channelsMatcher(expectedChannels),
|
|
"SystemChannel": BeNil(),
|
|
}))
|
|
}
|
|
|
|
func channelsMatcher(channels []string) types.GomegaMatcher {
|
|
if len(channels) == 0 {
|
|
return BeEmpty()
|
|
}
|
|
matchers := make([]types.GomegaMatcher, len(channels))
|
|
for i, channel := range channels {
|
|
matchers[i] = channelInfoShortMatcher(channel)
|
|
}
|
|
return ConsistOf(matchers)
|
|
}
|
|
|
|
func channelInfoShortMatcher(channel string) types.GomegaMatcher {
|
|
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
|
|
"Name": Equal(channel),
|
|
"URL": Equal(fmt.Sprintf("/participation/v1/channels/%s", channel)),
|
|
})
|
|
}
|
|
|
|
// JoinOrdererJoinPeersAppChannel Joins an orderer to a channel for which the genesis block was created by the network
|
|
// bootstrap. It assumes a channel with one orderer. It waits for a leader (single orderer, always node=1), and then
|
|
// joins all the peers to the channel.
|
|
func JoinOrdererJoinPeersAppChannel(network *nwo.Network, channelID string, orderer *nwo.Orderer, ordererRunner *ginkgomon.Runner) {
|
|
appGenesisBlock := network.LoadAppChannelGenesisBlock(channelID)
|
|
expectedChannelInfo := ChannelInfo{
|
|
Name: channelID,
|
|
URL: fmt.Sprintf("/participation/v1/channels/%s", channelID),
|
|
Status: "active",
|
|
ConsensusRelation: "consenter",
|
|
Height: 1,
|
|
}
|
|
Join(network, orderer, channelID, appGenesisBlock, expectedChannelInfo)
|
|
|
|
ginkgo.By(fmt.Sprintf("waiting for leader on channel %s", channelID))
|
|
Eventually(ordererRunner.Err(), network.EventuallyTimeout, time.Second).Should(
|
|
gbytes.Say(fmt.Sprintf("Raft leader changed: 0 -> 1 channel=%s node=1", channelID)))
|
|
|
|
ginkgo.By(fmt.Sprintf("joining peers to the channel %s", channelID))
|
|
peers := network.PeersWithChannel(channelID)
|
|
network.JoinChannel(channelID, orderer, peers...)
|
|
}
|
|
|
|
// JoinOrdererAppChannel Joins an orderer to a channel for which the genesis block was created by the network
|
|
// bootstrap. It assumes a channel with one orderer. It waits for a leader (single orderer, always node=1).
|
|
func JoinOrdererAppChannel(network *nwo.Network, channelID string, orderer *nwo.Orderer, ordererRunner *ginkgomon.Runner) {
|
|
appGenesisBlock := network.LoadAppChannelGenesisBlock(channelID)
|
|
expectedChannelInfo := ChannelInfo{
|
|
Name: channelID,
|
|
URL: fmt.Sprintf("/participation/v1/channels/%s", channelID),
|
|
Status: "active",
|
|
ConsensusRelation: "consenter",
|
|
Height: 1,
|
|
}
|
|
Join(network, orderer, channelID, appGenesisBlock, expectedChannelInfo)
|
|
|
|
ginkgo.By(fmt.Sprintf("waiting for leader on channel %s", channelID))
|
|
Eventually(ordererRunner.Err(), network.EventuallyTimeout, time.Second).Should(
|
|
gbytes.Say(fmt.Sprintf("Raft leader changed: 0 -> 1 channel=%s node=1", channelID)))
|
|
}
|
|
|
|
// JoinOrderersAppChannelCluster Joins a set of orderers to a channel for which the genesis block was created by the network
|
|
// bootstrap. It assumes a channel with one or more orderers (a cluster).
|
|
func JoinOrderersAppChannelCluster(network *nwo.Network, channelID string, orderers ...*nwo.Orderer) {
|
|
appGenesisBlock := network.LoadAppChannelGenesisBlock(channelID)
|
|
for _, orderer := range orderers {
|
|
expectedChannelInfo := ChannelInfo{
|
|
Name: channelID,
|
|
URL: fmt.Sprintf("/participation/v1/channels/%s", channelID),
|
|
Status: "active",
|
|
ConsensusRelation: "consenter",
|
|
Height: 1,
|
|
}
|
|
Join(network, orderer, channelID, appGenesisBlock, expectedChannelInfo)
|
|
}
|
|
}
|