Skip to content

Commit 36e5140

Browse files
committed
[FAB-5329] Able to instantiate on a taken chaincode ID
This fix appends a hash of the Docker image name to the image name to ensure a chaincode with different capitalization does not override the container of an already instantiated chaincode. This problem was introduced by the need to convert all Docker image names to lowercase. The Docker container name will retain its capitalization as that restriction only applies to the image name. Change-Id: I854f80b2f0e0269d9bbc60725f82b7e5a804b6fd Signed-off-by: Will Lahti <[email protected]>
1 parent d6b54c8 commit 36e5140

File tree

5 files changed

+104
-34
lines changed

5 files changed

+104
-34
lines changed

core/container/api/core.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import (
2727
type BuildSpecFactory func() (io.Reader, error)
2828
type PrelaunchFunc func() error
2929

30-
//abstract virtual image for supporting arbitrary virual machines
30+
//VM is an abstract virtual image for supporting arbitrary virual machines
3131
type VM interface {
3232
Deploy(ctxt context.Context, ccid ccintf.CCID, args []string, env []string, reader io.Reader) error
3333
Start(ctxt context.Context, ccid ccintf.CCID, args []string, env []string, builder BuildSpecFactory, preLaunchFunc PrelaunchFunc) error
3434
Stop(ctxt context.Context, ccid ccintf.CCID, timeout uint, dontkill bool, dontremove bool) error
3535
Destroy(ctxt context.Context, ccid ccintf.CCID, force bool, noprune bool) error
36-
GetVMName(ccID ccintf.CCID) (string, error)
36+
GetVMName(ccID ccintf.CCID, format func(string) (string, error)) (string, error)
3737
}

core/container/controller.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func VMCProcess(ctxt context.Context, vmtype string, req VMCReqIntf) (interface{
245245
go func() {
246246
defer close(c)
247247

248-
id, err := v.GetVMName(req.getCCID())
248+
id, err := v.GetVMName(req.getCCID(), nil)
249249
if err != nil {
250250
resp = VMCResp{Err: err}
251251
return

core/container/dockercontroller/dockercontroller.go

+47-25
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package dockercontroller
1818

1919
import (
2020
"bytes"
21+
"encoding/hex"
2122
"fmt"
2223
"io"
2324
"strings"
@@ -29,6 +30,7 @@ import (
2930

3031
"github.com/fsouza/go-dockerclient"
3132
"github.com/hyperledger/fabric/common/flogging"
33+
"github.com/hyperledger/fabric/common/util"
3234
container "github.com/hyperledger/fabric/core/container/api"
3335
"github.com/hyperledger/fabric/core/container/ccintf"
3436
cutil "github.com/hyperledger/fabric/core/container/util"
@@ -40,6 +42,8 @@ import (
4042
var (
4143
dockerLogger = flogging.MustGetLogger("dockercontroller")
4244
hostConfig *docker.HostConfig
45+
vmRegExp = regexp.MustCompile("[^a-zA-Z0-9-_.]")
46+
imageRegExp = regexp.MustCompile("^[a-z0-9]+(([._-][a-z0-9]+)+)?$")
4347
)
4448

4549
// getClient returns an instance that implements dockerClient interface
@@ -163,7 +167,7 @@ func (vm *DockerVM) createContainer(ctxt context.Context, client dockerClient,
163167

164168
func (vm *DockerVM) deployImage(client dockerClient, ccid ccintf.CCID,
165169
args []string, env []string, reader io.Reader) error {
166-
id, err := vm.GetVMName(ccid)
170+
id, err := vm.GetVMName(ccid, formatImageName)
167171
if err != nil {
168172
return err
169173
}
@@ -208,7 +212,7 @@ func (vm *DockerVM) Deploy(ctxt context.Context, ccid ccintf.CCID,
208212
//Start starts a container using a previously created docker image
209213
func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID,
210214
args []string, env []string, builder container.BuildSpecFactory, prelaunchFunc container.PrelaunchFunc) error {
211-
imageID, err := vm.GetVMName(ccid)
215+
imageID, err := vm.GetVMName(ccid, formatImageName)
212216
if err != nil {
213217
return err
214218
}
@@ -219,7 +223,11 @@ func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID,
219223
return err
220224
}
221225

222-
containerID := strings.Replace(imageID, ":", "_", -1)
226+
containerID, err := vm.GetVMName(ccid, nil)
227+
if err != nil {
228+
return err
229+
}
230+
223231
attachStdout := viper.GetBool("vm.docker.attachStdout")
224232

225233
//stop,force remove if necessary
@@ -349,7 +357,7 @@ func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID,
349357

350358
//Stop stops a running chaincode
351359
func (vm *DockerVM) Stop(ctxt context.Context, ccid ccintf.CCID, timeout uint, dontkill bool, dontremove bool) error {
352-
id, err := vm.GetVMName(ccid)
360+
id, err := vm.GetVMName(ccid, nil)
353361
if err != nil {
354362
return err
355363
}
@@ -395,7 +403,7 @@ func (vm *DockerVM) stopInternal(ctxt context.Context, client dockerClient,
395403

396404
//Destroy destroys an image
397405
func (vm *DockerVM) Destroy(ctxt context.Context, ccid ccintf.CCID, force bool, noprune bool) error {
398-
id, err := vm.GetVMName(ccid)
406+
id, err := vm.GetVMName(ccid, formatImageName)
399407
if err != nil {
400408
return err
401409
}
@@ -418,34 +426,48 @@ func (vm *DockerVM) Destroy(ctxt context.Context, ccid ccintf.CCID, force bool,
418426
return err
419427
}
420428

421-
//GetVMName generates the docker image from peer information given the hashcode. This is needed to
422-
//keep image name's unique in a single host, multi-peer environment (such as a development environment)
423-
func (vm *DockerVM) GetVMName(ccid ccintf.CCID) (string, error) {
429+
// GetVMName generates the VM name from peer information. It accepts a format
430+
// function parameter to allow different formatting based on the desired use of
431+
// the name.
432+
func (vm *DockerVM) GetVMName(ccid ccintf.CCID, format func(string) (string, error)) (string, error) {
424433
name := ccid.GetName()
425434

426-
//Convert to lowercase and replace any invalid characters with "-"
427-
r := regexp.MustCompile("[^a-zA-Z0-9-_.]")
428-
435+
// replace any invalid characters with "-"
429436
if ccid.NetworkID != "" && ccid.PeerID != "" {
430-
name = strings.ToLower(
431-
r.ReplaceAllString(
432-
fmt.Sprintf("%s-%s-%s", ccid.NetworkID, ccid.PeerID, name), "-"))
437+
name = vmRegExp.ReplaceAllString(
438+
fmt.Sprintf("%s-%s-%s", ccid.NetworkID, ccid.PeerID, name), "-")
433439
} else if ccid.NetworkID != "" {
434-
name = strings.ToLower(
435-
r.ReplaceAllString(
436-
fmt.Sprintf("%s-%s", ccid.NetworkID, name), "-"))
440+
name = vmRegExp.ReplaceAllString(
441+
fmt.Sprintf("%s-%s", ccid.NetworkID, name), "-")
437442
} else if ccid.PeerID != "" {
438-
name = strings.ToLower(
439-
r.ReplaceAllString(
440-
fmt.Sprintf("%s-%s", ccid.PeerID, name), "-"))
443+
name = vmRegExp.ReplaceAllString(
444+
fmt.Sprintf("%s-%s", ccid.PeerID, name), "-")
441445
}
442446

443-
// Check name complies with Docker's repository naming rules
444-
r = regexp.MustCompile("^[a-z0-9]+(([._-][a-z0-9]+)+)?$")
447+
if format != nil {
448+
formattedName, err := format(name)
449+
if err != nil {
450+
return formattedName, err
451+
}
452+
// check to ensure format function didn't add any invalid characters
453+
name = vmRegExp.ReplaceAllString(formattedName, "-")
454+
}
455+
return name, nil
456+
}
445457

446-
if !r.MatchString(name) {
458+
// formatImageName formats the docker image from peer information. This is
459+
// needed to keep image (repository) names unique in a single host, multi-peer
460+
// environment (such as a development environment). It computes the hash for the
461+
// supplied image name and then appends it to the lowercase image name to ensure
462+
// uniqueness.
463+
func formatImageName(name string) (string, error) {
464+
imageName := strings.ToLower(fmt.Sprintf("%s-%s", name, hex.EncodeToString(util.ComputeSHA256([]byte(name)))))
465+
466+
// Check that name complies with Docker's repository naming rules
467+
if !imageRegExp.MatchString(imageName) {
447468
dockerLogger.Errorf("Error constructing Docker VM Name. '%s' breaks Docker's repository naming rules", name)
448-
return name, fmt.Errorf("Error constructing Docker VM Name. '%s' breaks Docker's repository naming rules", name)
469+
return imageName, fmt.Errorf("Error constructing Docker VM Name. '%s' breaks Docker's repository naming rules", imageName)
449470
}
450-
return name, nil
471+
472+
return imageName, nil
451473
}

core/container/dockercontroller/dockercontroller_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"bytes"
2222
"compress/gzip"
2323
"context"
24+
"encoding/hex"
2425
"errors"
2526
"fmt"
2627
"io"
@@ -226,6 +227,39 @@ func Test_Destroy(t *testing.T) {
226227
testerr(t, err, true)
227228
}
228229

230+
type testCase struct {
231+
name string
232+
ccid ccintf.CCID
233+
formatFunc func(string) (string, error)
234+
expectedOutput string
235+
}
236+
237+
func TestGetVMName(t *testing.T) {
238+
dvm := DockerVM{}
239+
var tc []testCase
240+
241+
tc = append(tc,
242+
testCase{"mycc", ccintf.CCID{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "mycc"}}, NetworkID: "dev", PeerID: "peer0", Version: "1.0"}, formatImageName, fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-mycc-1.0"))))},
243+
testCase{"mycc-nonetworkid", ccintf.CCID{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "mycc"}}, PeerID: "peer1", Version: "1.0"}, formatImageName, fmt.Sprintf("%s-%s", "peer1-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("peer1-mycc-1.0"))))},
244+
testCase{"myCC", ccintf.CCID{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "myCC"}}, NetworkID: "Dev", PeerID: "Peer0", Version: "1.0"}, formatImageName, fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev-Peer0-myCC-1.0"))))},
245+
testCase{"mycc-nopeerid", ccintf.CCID{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "mycc"}}, NetworkID: "dev", Version: "1.0"}, formatImageName, fmt.Sprintf("%s-%s", "dev-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-mycc-1.0"))))},
246+
testCase{"myCC", ccintf.CCID{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "myCC"}}, NetworkID: "dev", PeerID: "peer0", Version: "1.0"}, formatImageName, fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-myCC-1.0"))))},
247+
testCase{"myCC-preserveCase", ccintf.CCID{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "myCC"}}, NetworkID: "Dev", PeerID: "Peer0", Version: "1.0"}, nil, fmt.Sprintf("%s", "Dev-Peer0-myCC-1.0")},
248+
testCase{"invalidCharsFormatFunction", ccintf.CCID{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "myCC"}}, NetworkID: "Dev", PeerID: "Peer0", Version: "1.0"}, formatInvalidChars, fmt.Sprintf("%s", "inv-lid-character--")})
249+
250+
for _, test := range tc {
251+
name, err := dvm.GetVMName(test.ccid, test.formatFunc)
252+
assert.Nil(t, err, "Expected nil error")
253+
assert.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name)
254+
}
255+
256+
}
257+
258+
func TestFormatImageName_invalidChars(t *testing.T) {
259+
_, err := formatImageName("invalid*chars")
260+
assert.NotNil(t, err, "Expected error")
261+
}
262+
229263
func getCodeChainBytesInMem() io.Reader {
230264
startTime := time.Now()
231265
inputbuf := bytes.NewBuffer(nil)
@@ -322,3 +356,7 @@ func (c *mockClient) RemoveContainer(opts docker.RemoveContainerOptions) error {
322356
}
323357
return nil
324358
}
359+
360+
func formatInvalidChars(name string) (string, error) {
361+
return "inv@lid*character$/", nil
362+
}

core/container/inproccontroller/inproccontroller.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (vm *InprocVM) Deploy(ctxt context.Context, ccid ccintf.CCID, args []string
9393
return fmt.Errorf(fmt.Sprintf("%s system chaincode does not contain chaincode instance", path))
9494
}
9595

96-
instName, _ := vm.GetVMName(ccid)
96+
instName, _ := vm.GetVMName(ccid, nil)
9797
_, err := vm.getInstance(ctxt, ipctemplate, instName, args, env)
9898

9999
//FUTURE ... here is where we might check code for safety
@@ -163,7 +163,7 @@ func (vm *InprocVM) Start(ctxt context.Context, ccid ccintf.CCID, args []string,
163163
return fmt.Errorf(fmt.Sprintf("%s not registered", path))
164164
}
165165

166-
instName, _ := vm.GetVMName(ccid)
166+
instName, _ := vm.GetVMName(ccid, nil)
167167

168168
ipc, err := vm.getInstance(ctxt, ipctemplate, instName, args, env)
169169

@@ -211,7 +211,7 @@ func (vm *InprocVM) Stop(ctxt context.Context, ccid ccintf.CCID, timeout uint, d
211211
return fmt.Errorf("%s not registered", path)
212212
}
213213

214-
instName, _ := vm.GetVMName(ccid)
214+
instName, _ := vm.GetVMName(ccid, nil)
215215

216216
ipc := instRegistry[instName]
217217

@@ -236,7 +236,17 @@ func (vm *InprocVM) Destroy(ctxt context.Context, ccid ccintf.CCID, force bool,
236236
return nil
237237
}
238238

239-
//GetVMName ignores the peer and network name as it just needs to be unique in process
240-
func (vm *InprocVM) GetVMName(ccid ccintf.CCID) (string, error) {
241-
return ccid.GetName(), nil
239+
// GetVMName ignores the peer and network name as it just needs to be unique in
240+
// process. It accepts a format function parameter to allow different
241+
// formatting based on the desired use of the name.
242+
func (vm *InprocVM) GetVMName(ccid ccintf.CCID, format func(string) (string, error)) (string, error) {
243+
name := ccid.GetName()
244+
if format != nil {
245+
formattedName, err := format(name)
246+
if err != nil {
247+
return formattedName, err
248+
}
249+
name = formattedName
250+
}
251+
return name, nil
242252
}

0 commit comments

Comments
 (0)