Skip to content

Commit c8785e3

Browse files
author
Jason Yellick
committed
[FAB-4167] Expose config update compute via REST
As part of FAB-1678, the tool needs to support computing a config update based on the contents of two configs. This CR adds a new REST endpoint for handling this computation. It add the target /configtxlator/compute/update-from-configs which takes a POST of multipart/formdata with two files, the first for the field 'original' and the second for the form 'updated'. These should both be proto marshaled common.Config structures. A final 'channel' parameter may be supplied to set the channel id for the update. The server then invokes the configtxlator.update package to compute the config update based on the inputs, and returns it as a marshaled common.ConfigUpdate message. Change-Id: I0ca13d6de41da8b0ce9bfc66685476b42a6991e6 Signed-off-by: Jason Yellick <[email protected]>
1 parent 5fb91b5 commit c8785e3

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rest
18+
19+
import (
20+
"fmt"
21+
"io/ioutil"
22+
"net/http"
23+
24+
"github.com/hyperledger/fabric/common/tools/configtxlator/update"
25+
26+
cb "github.com/hyperledger/fabric/protos/common"
27+
28+
"github.com/golang/protobuf/proto"
29+
)
30+
31+
func fieldBytes(fieldName string, r *http.Request) ([]byte, error) {
32+
fieldFile, _, err := r.FormFile(fieldName)
33+
if err != nil {
34+
return nil, err
35+
}
36+
defer fieldFile.Close()
37+
38+
return ioutil.ReadAll(fieldFile)
39+
}
40+
41+
func fieldConfigProto(fieldName string, r *http.Request) (*cb.Config, error) {
42+
fieldBytes, err := fieldBytes(fieldName, r)
43+
if err != nil {
44+
return nil, fmt.Errorf("error reading field bytes: %s", err)
45+
}
46+
47+
config := &cb.Config{}
48+
err = proto.Unmarshal(fieldBytes, config)
49+
if err != nil {
50+
return nil, fmt.Errorf("error unmarshaling field bytes: %s", err)
51+
}
52+
53+
return config, nil
54+
}
55+
56+
func ComputeUpdateFromConfigs(w http.ResponseWriter, r *http.Request) {
57+
originalConfig, err := fieldConfigProto("original", r)
58+
if err != nil {
59+
w.WriteHeader(http.StatusBadRequest)
60+
fmt.Fprintf(w, "Error with field 'original': %s\n", err)
61+
return
62+
}
63+
64+
updatedConfig, err := fieldConfigProto("updated", r)
65+
if err != nil {
66+
w.WriteHeader(http.StatusBadRequest)
67+
fmt.Fprintf(w, "Error with field 'updated': %s\n", err)
68+
return
69+
}
70+
71+
configUpdate, err := update.Compute(originalConfig, updatedConfig)
72+
if err != nil {
73+
w.WriteHeader(http.StatusBadRequest)
74+
fmt.Fprintf(w, "Error computing update: %s\n", err)
75+
return
76+
}
77+
78+
configUpdate.ChannelId = r.FormValue("channel")
79+
80+
encoded, err := proto.Marshal(configUpdate)
81+
if err != nil {
82+
w.WriteHeader(http.StatusInternalServerError)
83+
fmt.Fprintf(w, "Error marshaling config update: %s\n", err)
84+
return
85+
}
86+
87+
w.WriteHeader(http.StatusOK)
88+
w.Header().Set("Content-Type", "application/octet-stream")
89+
w.Write(encoded)
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rest
18+
19+
import (
20+
"bytes"
21+
"mime/multipart"
22+
"net/http"
23+
"net/http/httptest"
24+
"testing"
25+
26+
cb "github.com/hyperledger/fabric/protos/common"
27+
"github.com/hyperledger/fabric/protos/utils"
28+
29+
"github.com/stretchr/testify/assert"
30+
)
31+
32+
func TestProtolatorComputeConfigUpdate(t *testing.T) {
33+
originalConfig := utils.MarshalOrPanic(&cb.Config{
34+
ChannelGroup: &cb.ConfigGroup{
35+
ModPolicy: "foo",
36+
},
37+
})
38+
39+
updatedConfig := utils.MarshalOrPanic(&cb.Config{
40+
ChannelGroup: &cb.ConfigGroup{
41+
ModPolicy: "bar",
42+
},
43+
})
44+
45+
buffer := &bytes.Buffer{}
46+
mpw := multipart.NewWriter(buffer)
47+
48+
ffw, err := mpw.CreateFormFile("original", "foo")
49+
assert.NoError(t, err)
50+
_, err = bytes.NewReader(originalConfig).WriteTo(ffw)
51+
assert.NoError(t, err)
52+
53+
ffw, err = mpw.CreateFormFile("updated", "bar")
54+
assert.NoError(t, err)
55+
_, err = bytes.NewReader(updatedConfig).WriteTo(ffw)
56+
assert.NoError(t, err)
57+
58+
err = mpw.Close()
59+
assert.NoError(t, err)
60+
61+
req, err := http.NewRequest("POST", "/configtxlator/compute/update-from-configs", buffer)
62+
assert.NoError(t, err)
63+
64+
req.Header.Set("Content-Type", mpw.FormDataContentType())
65+
rec := httptest.NewRecorder()
66+
r := NewRouter()
67+
r.ServeHTTP(rec, req)
68+
69+
assert.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
70+
}
71+
72+
func TestProtolatorMissingOriginal(t *testing.T) {
73+
updatedConfig := utils.MarshalOrPanic(&cb.Config{
74+
ChannelGroup: &cb.ConfigGroup{
75+
ModPolicy: "bar",
76+
},
77+
})
78+
79+
buffer := &bytes.Buffer{}
80+
mpw := multipart.NewWriter(buffer)
81+
82+
ffw, err := mpw.CreateFormFile("updated", "bar")
83+
assert.NoError(t, err)
84+
_, err = bytes.NewReader(updatedConfig).WriteTo(ffw)
85+
assert.NoError(t, err)
86+
87+
err = mpw.Close()
88+
assert.NoError(t, err)
89+
90+
req, err := http.NewRequest("POST", "/configtxlator/compute/update-from-configs", buffer)
91+
assert.NoError(t, err)
92+
93+
req.Header.Set("Content-Type", mpw.FormDataContentType())
94+
rec := httptest.NewRecorder()
95+
r := NewRouter()
96+
r.ServeHTTP(rec, req)
97+
98+
assert.Equal(t, http.StatusBadRequest, rec.Code)
99+
}
100+
101+
func TestProtolatorMissingUpdated(t *testing.T) {
102+
originalConfig := utils.MarshalOrPanic(&cb.Config{
103+
ChannelGroup: &cb.ConfigGroup{
104+
ModPolicy: "bar",
105+
},
106+
})
107+
108+
buffer := &bytes.Buffer{}
109+
mpw := multipart.NewWriter(buffer)
110+
111+
ffw, err := mpw.CreateFormFile("original", "bar")
112+
assert.NoError(t, err)
113+
_, err = bytes.NewReader(originalConfig).WriteTo(ffw)
114+
assert.NoError(t, err)
115+
116+
err = mpw.Close()
117+
assert.NoError(t, err)
118+
119+
req, err := http.NewRequest("POST", "/configtxlator/compute/update-from-configs", buffer)
120+
assert.NoError(t, err)
121+
122+
req.Header.Set("Content-Type", mpw.FormDataContentType())
123+
rec := httptest.NewRecorder()
124+
r := NewRouter()
125+
r.ServeHTTP(rec, req)
126+
127+
assert.Equal(t, http.StatusBadRequest, rec.Code)
128+
}
129+
130+
func TestProtolatorCorruptProtos(t *testing.T) {
131+
originalConfig := []byte("Garbage")
132+
updatedConfig := []byte("MoreGarbage")
133+
134+
buffer := &bytes.Buffer{}
135+
mpw := multipart.NewWriter(buffer)
136+
137+
ffw, err := mpw.CreateFormFile("original", "bar")
138+
assert.NoError(t, err)
139+
_, err = bytes.NewReader(originalConfig).WriteTo(ffw)
140+
assert.NoError(t, err)
141+
142+
ffw, err = mpw.CreateFormFile("updated", "bar")
143+
assert.NoError(t, err)
144+
_, err = bytes.NewReader(updatedConfig).WriteTo(ffw)
145+
assert.NoError(t, err)
146+
147+
err = mpw.Close()
148+
assert.NoError(t, err)
149+
150+
req, err := http.NewRequest("POST", "/configtxlator/compute/update-from-configs", buffer)
151+
assert.NoError(t, err)
152+
153+
req.Header.Set("Content-Type", mpw.FormDataContentType())
154+
rec := httptest.NewRecorder()
155+
r := NewRouter()
156+
r.ServeHTTP(rec, req)
157+
158+
assert.Equal(t, http.StatusBadRequest, rec.Code)
159+
}

common/tools/configtxlator/rest/router.go

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ func NewRouter() *mux.Router {
3232
router.
3333
HandleFunc("/protolator/decode/{msgName}", Decode).
3434
Methods("POST")
35+
router.
36+
HandleFunc("/configtxlator/compute/update-from-configs", ComputeUpdateFromConfigs).
37+
Methods("POST")
3538

3639
return router
3740
}

0 commit comments

Comments
 (0)