Skip to content

Commit 4f84daa

Browse files
committed
added to OSN new handler for fetch a block of channel
part 3 Signed-off-by: Fedor Partanskiy <fredprtnsk@gmail.com>
1 parent 797abb3 commit 4f84daa

File tree

7 files changed

+257
-6
lines changed

7 files changed

+257
-6
lines changed

cmd/osnadmin/main.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"io"
1717
"net/http"
1818
"os"
19+
"strconv"
1920

2021
"github.com/hyperledger/fabric-protos-go-apiv2/common"
2122
"github.com/hyperledger/fabric/internal/osnadmin"
@@ -63,6 +64,12 @@ func executeForArgs(args []string) (output string, exit int, err error) {
6364
configUpdateEnvelopePath := update.Flag("config-update-envelope", "Path to the file containing an up-to-date config update envelope for the channel").Short('e').Required().String()
6465
tlsHandshakeTimeShift := update.Flag("tlsHandshakeTimeShift", "The amount of time to shift backwards for certificate expiration checks during TLS handshakes with the orderer endpoint").Short('t').Default("0").Duration()
6566

67+
fetch := channel.Command("fetch", "Fetch a specified block, writing it to a file.")
68+
fetchChannelID := fetch.Flag("channelID", "Channel ID").Short('c').Required().String()
69+
fetchBlockID := fetch.Flag("blockID", "Block ID - <newest|oldest|config|(number)>").Short('b').Required().String()
70+
fetchOutputFile := fetch.Flag("outputfile", "Puth to a file.").Short('f').Required().String()
71+
tlsHandshakeTimeShift1 := fetch.Flag("tlsHandshakeTimeShift", "The amount of time to shift backwards for certificate expiration checks during TLS handshakes with the orderer endpoint").Short('t').Default("0").Duration()
72+
6673
command, err := app.Parse(args)
6774
if err != nil {
6875
return "", 1, err
@@ -141,6 +148,14 @@ func executeForArgs(args []string) (output string, exit int, err error) {
141148
resp, err = osnadmin.Remove(osnURL, *removeChannelID, caCertPool, tlsClientCert)
142149
case update.FullCommand():
143150
resp, err = osnadmin.Update(osnURL, marshaledConfigEnvelope, caCertPool, tlsClientCert, *tlsHandshakeTimeShift)
151+
case fetch.FullCommand():
152+
if *fetchBlockID != "newest" && *fetchBlockID != "oldest" && *fetchBlockID != "config" {
153+
_, err = strconv.Atoi(*fetchBlockID)
154+
if err != nil {
155+
return "", 1, fmt.Errorf("'%s' not equal <newest|oldest|config|(number)>", *fetchBlockID)
156+
}
157+
}
158+
resp, err = osnadmin.Fetch(osnURL, *fetchChannelID, *fetchBlockID, caCertPool, tlsClientCert, *tlsHandshakeTimeShift1)
144159
}
145160
if err != nil {
146161
return errorOutput(err), 1, nil
@@ -151,22 +166,28 @@ func executeForArgs(args []string) (output string, exit int, err error) {
151166
return errorOutput(err), 1, nil
152167
}
153168

154-
output, err = responseOutput(!*noStatus, resp.StatusCode, bodyBytes)
169+
output, err = responseOutput(!*noStatus, resp.StatusCode, bodyBytes, *fetchOutputFile)
155170
if err != nil {
156171
return errorOutput(err), 1, nil
157172
}
158173

159174
return output, 0, nil
160175
}
161176

162-
func responseOutput(showStatus bool, statusCode int, responseBody []byte) (string, error) {
177+
func responseOutput(showStatus bool, statusCode int, responseBody []byte, outputFile string) (string, error) {
163178
var buffer bytes.Buffer
164179
if showStatus {
165180
fmt.Fprintf(&buffer, "Status: %d\n", statusCode)
166181
}
167182
if len(responseBody) != 0 {
168-
if err := json.Indent(&buffer, responseBody, "", "\t"); err != nil {
169-
return "", err
183+
if statusCode == http.StatusOK && outputFile != "" {
184+
if err := os.WriteFile(outputFile, responseBody, 0o644); err != nil {
185+
return "", err
186+
}
187+
} else {
188+
if err := json.Indent(&buffer, responseBody, "", "\t"); err != nil {
189+
return "", err
190+
}
170191
}
171192
}
172193
return buffer.String(), nil

cmd/osnadmin/main_test.go

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
cb "github.com/hyperledger/fabric-protos-go-apiv2/common"
2323
"github.com/hyperledger/fabric/cmd/osnadmin/mocks"
2424
"github.com/hyperledger/fabric/common/crypto/tlsgen"
25+
. "github.com/hyperledger/fabric/internal/test"
2526
"github.com/hyperledger/fabric/orderer/common/channelparticipation"
2627
"github.com/hyperledger/fabric/orderer/common/localconfig"
2728
"github.com/hyperledger/fabric/orderer/common/types"
@@ -694,7 +695,7 @@ var _ = Describe("osnadmin", func() {
694695
tlsConfig = nil
695696
})
696697

697-
It("uses the channel participation API to join a channel", func() {
698+
It("uses the channel participation API to update a channel", func() {
698699
args := []string{
699700
"channel",
700701
"update",
@@ -713,6 +714,137 @@ var _ = Describe("osnadmin", func() {
713714
})
714715
})
715716

717+
Describe("Fetch", func() {
718+
var (
719+
blockPath string
720+
block *cb.Block
721+
)
722+
723+
BeforeEach(func() {
724+
blockPath = filepath.Join(tempDir, "block.pb")
725+
block = blockWithGroups(
726+
map[string]*cb.ConfigGroup{
727+
"Application": {},
728+
},
729+
"testing123",
730+
)
731+
mockChannelManagement.FetchBlockReturns(block, nil)
732+
})
733+
734+
AfterEach(func() {
735+
_ = os.Remove(blockPath)
736+
})
737+
738+
It("uses the channel participation API to fetch a block", func() {
739+
args := []string{
740+
"channel",
741+
"fetch",
742+
"--outputfile", blockPath,
743+
"--channelID", channelID,
744+
"--blockID", "100",
745+
"--orderer-address", ordererURL,
746+
"--ca-file", ordererCACert,
747+
"--client-cert", clientCert,
748+
"--client-key", clientKey,
749+
}
750+
output, exit, err := executeForArgs(args)
751+
Expect(err).NotTo(HaveOccurred())
752+
Expect(exit).To(Equal(0))
753+
Expect(output).To(Equal(fmt.Sprintf("Status: %d\n", 200)))
754+
755+
blockBytes, err := os.ReadFile(blockPath)
756+
Expect(err).NotTo(HaveOccurred())
757+
758+
b := &cb.Block{}
759+
err = proto.Unmarshal(blockBytes, b)
760+
Expect(err).NotTo(HaveOccurred())
761+
Expect(b).To(ProtoEqual(block))
762+
})
763+
764+
Context("when the block is empty", func() {
765+
BeforeEach(func() {
766+
block = &cb.Block{}
767+
mockChannelManagement.FetchBlockReturns(block, nil)
768+
})
769+
770+
It("returns with exit code 1 and prints the error", func() {
771+
args := []string{
772+
"channel",
773+
"fetch",
774+
"--outputfile", blockPath,
775+
"--channelID", channelID,
776+
"--blockID", "100",
777+
"--orderer-address", ordererURL,
778+
"--ca-file", ordererCACert,
779+
"--client-cert", clientCert,
780+
"--client-key", clientKey,
781+
}
782+
output, exit, err := executeForArgs(args)
783+
Expect(err).NotTo(HaveOccurred())
784+
Expect(exit).To(Equal(0))
785+
Expect(output).To(Equal(fmt.Sprintf("Status: %d\n", 200)))
786+
787+
blockBytes, err := os.ReadFile(blockPath)
788+
Expect(err).To(HaveOccurred())
789+
Expect(blockBytes).To(BeNil())
790+
})
791+
})
792+
793+
Context("when fetch the channel fails", func() {
794+
BeforeEach(func() {
795+
mockChannelManagement.FetchBlockReturns(nil, types.ErrChannelNotExist)
796+
})
797+
798+
It("returns 404 does not exist", func() {
799+
args := []string{
800+
"channel",
801+
"fetch",
802+
"--outputfile", blockPath,
803+
"--channelID", channelID,
804+
"--blockID", "100",
805+
"--orderer-address", ordererURL,
806+
"--ca-file", ordererCACert,
807+
"--client-cert", clientCert,
808+
"--client-key", clientKey,
809+
}
810+
output, exit, err := executeForArgs(args)
811+
expectedOutput := types.ErrorResponse{
812+
Error: "channel does not exist",
813+
}
814+
checkStatusOutput(output, exit, err, 404, expectedOutput)
815+
})
816+
})
817+
818+
Context("when TLS is disabled", func() {
819+
BeforeEach(func() {
820+
tlsConfig = nil
821+
})
822+
823+
It("uses the channel participation API to fetch a block", func() {
824+
args := []string{
825+
"channel",
826+
"fetch",
827+
"--outputfile", blockPath,
828+
"--channelID", channelID,
829+
"--blockID", "100",
830+
"--orderer-address", ordererURL,
831+
}
832+
output, exit, err := executeForArgs(args)
833+
Expect(err).NotTo(HaveOccurred())
834+
Expect(exit).To(Equal(0))
835+
Expect(output).To(Equal(fmt.Sprintf("Status: %d\n", 200)))
836+
837+
blockBytes, err := os.ReadFile(blockPath)
838+
Expect(err).NotTo(HaveOccurred())
839+
840+
b := &cb.Block{}
841+
err = proto.Unmarshal(blockBytes, b)
842+
Expect(err).NotTo(HaveOccurred())
843+
Expect(b).To(ProtoEqual(block))
844+
})
845+
})
846+
})
847+
716848
Describe("Flags", func() {
717849
It("accepts short versions of the --orderer-address, --channelID, and --config-block flags", func() {
718850
configBlock := blockWithGroups(
@@ -750,6 +882,41 @@ var _ = Describe("osnadmin", func() {
750882
checkStatusOutput(output, exit, err, 201, expectedOutput)
751883
})
752884

885+
It("accepts short versions of the --channelID, --blockID, and --outputfile flags", func() {
886+
blockPath := filepath.Join(tempDir, "block.pb")
887+
block := blockWithGroups(
888+
map[string]*cb.ConfigGroup{
889+
"Application": {},
890+
},
891+
"testing123",
892+
)
893+
mockChannelManagement.FetchBlockReturns(block, nil)
894+
895+
args := []string{
896+
"channel",
897+
"fetch",
898+
"-f", blockPath,
899+
"-c", channelID,
900+
"-b", "oldest",
901+
"-o", ordererURL,
902+
"--ca-file", ordererCACert,
903+
"--client-cert", clientCert,
904+
"--client-key", clientKey,
905+
}
906+
output, exit, err := executeForArgs(args)
907+
Expect(err).NotTo(HaveOccurred())
908+
Expect(exit).To(Equal(0))
909+
Expect(output).To(Equal(fmt.Sprintf("Status: %d\n", 200)))
910+
911+
blockBytes, err := os.ReadFile(blockPath)
912+
Expect(err).NotTo(HaveOccurred())
913+
914+
b := &cb.Block{}
915+
err = proto.Unmarshal(blockBytes, b)
916+
Expect(err).NotTo(HaveOccurred())
917+
Expect(b).To(ProtoEqual(block))
918+
})
919+
753920
Context("when an unknown flag is used", func() {
754921
It("returns an error for long flags", func() {
755922
_, _, err := executeForArgs([]string{"channel", "list", "--bad-flag"})

docs/source/commands/osnadminchannel.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ The `osnadmin channel` command has the following subcommands:
1818
* join
1919
* list
2020
* remove
21+
* update
22+
* fetch
2123

2224
## osnadmin channel
2325
```
@@ -56,6 +58,9 @@ Subcommands:
5658
5759
channel update --channelID=CHANNELID --config-update-envelope=CONFIG-UPDATE-ENVELOPE [<flags>]
5860
Update an Ordering Service Node (OSN) to a channel.
61+
62+
channel fetch --channelID=CHANNELID --blockID=BLOCKID --outputfile=OUTPUTFILE [<flags>]
63+
Fetch a specified block, writing it to a file.
5964
```
6065

6166

@@ -169,6 +174,35 @@ Flags:
169174
handshakes with the orderer endpoint
170175
```
171176

177+
## osnadmin channel fetch
178+
```
179+
usage: osnadmin channel fetch --channelID=CHANNELID --blockID=BLOCKID --outputfile=OUTPUTFILE [<flags>]
180+
181+
Fetch a specified block, writing it to a file.
182+
183+
Flags:
184+
--help Show context-sensitive help (also try
185+
--help-long and --help-man).
186+
-o, --orderer-address=ORDERER-ADDRESS
187+
Admin endpoint of the OSN
188+
--ca-file=CA-FILE Path to file containing PEM-encoded TLS CA
189+
certificate(s) for the OSN
190+
--client-cert=CLIENT-CERT Path to file containing PEM-encoded X509 public
191+
key to use for mutual TLS communication with
192+
the OSN
193+
--client-key=CLIENT-KEY Path to file containing PEM-encoded private key
194+
to use for mutual TLS communication with the
195+
OSN
196+
--no-status Remove the HTTP status message from the command
197+
output
198+
-c, --channelID=CHANNELID Channel ID
199+
-b, --blockID=BLOCKID Block ID - <newest|oldest|config|(number)>
200+
-f, --outputfile=OUTPUTFILE Puth to a file.
201+
-t, --tlsHandshakeTimeShift=0 The amount of time to shift backwards for
202+
certificate expiration checks during TLS
203+
handshakes with the orderer endpoint
204+
```
205+
172206
## Example Usage
173207

174208
### osnadmin channel join examples

docs/wrappers/osnadmin_channel_preamble.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ The `osnadmin channel` command has the following subcommands:
1313
* join
1414
* list
1515
* remove
16+
* update
17+
* fetch

internal/osnadmin/fetch.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package osnadmin
8+
9+
import (
10+
"crypto/tls"
11+
"crypto/x509"
12+
"fmt"
13+
"net/http"
14+
"time"
15+
)
16+
17+
// Fetch get block from OSN.
18+
func Fetch(osnURL, channelID string, blockID string, caCertPool *x509.CertPool, tlsClientCert tls.Certificate, timeShift time.Duration) (*http.Response, error) {
19+
url := fmt.Sprintf("%s/participation/v1/channels/%s/blocks/%s", osnURL, channelID, blockID)
20+
21+
return httpGetTimeShift(url, caCertPool, tlsClientCert, timeShift)
22+
}

internal/osnadmin/httpclient.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ func httpGet(url string, caCertPool *x509.CertPool, tlsClientCert tls.Certificat
4545
client := httpClient(caCertPool, tlsClientCert, 0)
4646
return client.Get(url)
4747
}
48+
49+
func httpGetTimeShift(url string, caCertPool *x509.CertPool, tlsClientCert tls.Certificate, timeShift time.Duration) (*http.Response, error) {
50+
client := httpClient(caCertPool, tlsClientCert, timeShift)
51+
return client.Get(url)
52+
}

scripts/help_docs.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ generateOrCheck \
134134
docs/wrappers/configtxlator_postscript.md \
135135
"${commands[@]}"
136136

137-
commands=("osnadmin channel" "osnadmin channel join" "osnadmin channel list" "osnadmin channel remove" "osnadmin channel update")
137+
commands=("osnadmin channel" "osnadmin channel join" "osnadmin channel list" "osnadmin channel remove" "osnadmin channel update" "osnadmin channel fetch")
138138
generateOrCheck \
139139
docs/source/commands/osnadminchannel.md \
140140
docs/wrappers/osnadmin_channel_preamble.md \

0 commit comments

Comments
 (0)