Skip to content

Commit

Permalink
Merge pull request hyperledger-cello#583 from xichen1/chaincode-djang…
Browse files Browse the repository at this point in the history
…o-model

Refactor old cc pkg upload API
  • Loading branch information
yeasy authored Dec 2, 2023
2 parents 0cee896 + 137cac4 commit 5c8def2
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 76 deletions.
22 changes: 22 additions & 0 deletions src/api-engine/api/lib/peer/chaincode.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,25 @@ def query(self, orderer_url, orderer_tls_rootcert, channel_name, cc_name, args):
except Exception as e:
err_msg = "query failed for {}!".format(e)
raise Exception(err_msg)

def lifecycle_calculatepackageid(self, cc_path):
"""
calculate the chaincode packageid.
:param cc_path: where the chaincode package is
:return: calculated packageid
"""
try:
res = subprocess.Popen("{} lifecycle chaincode calculatepackageid {} "
.format(self.peer, cc_path),
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = res.communicate()
return_code = res.returncode
if return_code == 0:
content = str(stdout, encoding="utf-8")
return return_code, content
else:
stderr = str(stderr, encoding="utf-8")
return return_code, stderr
except Exception as e:
err_msg = "calculated chaincode packageid failed for {}!".format(e)
raise Exception(err_msg)
14 changes: 8 additions & 6 deletions src/api-engine/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,20 +804,22 @@ class ChainCode(models.Model):
editable=False,
unique=True
)
name = models.CharField(
help_text="name of chainCode", max_length=128
package_id = models.CharField(
help_text="package_id of chainCode", max_length=128,
editable=False,
unique=True
)
version = models.CharField(
help_text="version of chainCode", max_length=128
label = models.CharField(
help_text="label of chainCode", max_length=128
)
creator = models.CharField(
help_text="creator of chainCode", max_length=128
)
language = models.CharField(
help_text="language of chainCode", max_length=128
)
md5 = models.CharField(
help_text="md5 of chainCode", max_length=128
description = models.CharField(
help_text="description of chainCode", max_length=128, blank=True, null=True
)
create_ts = models.DateTimeField(
help_text="Create time of chainCode", auto_now_add=True
Expand Down
23 changes: 9 additions & 14 deletions src/api-engine/api/routes/chaincode/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from api.models import ChainCode
from api.common.serializers import ListResponseSerializer
import hashlib
import os


def upload_to(instance, filename):
Expand All @@ -18,25 +18,20 @@ class ChainCodeIDSerializer(serializers.Serializer):


class ChainCodePackageBody(serializers.Serializer):
name = serializers.CharField(max_length=128, required=True)
version = serializers.CharField(max_length=128, required=True)
language = serializers.CharField(max_length=128, required=True)
md5 = serializers.CharField(max_length=128, required=True)
file = serializers.FileField()

description = serializers.CharField(max_length=128, required=False)

def validate(self, attrs):
md5_get = self.md5_for_file(attrs["file"])
if md5_get != attrs["md5"]:
raise serializers.ValidationError("md5 not same.")
extension_get = self.extension_for_file(attrs["file"])
if not extension_get:
raise serializers.ValidationError("unsupported package type")
return super().validate(attrs)

@staticmethod
def md5_for_file(chunks):
md5 = hashlib.md5()
for data in chunks:
md5.update(data)
return md5.hexdigest()

def extension_for_file(file):
extension = file.name.endswith('.tar.gz')
return extension

class ChainCodeNetworkSerializer(serializers.Serializer):
id = serializers.UUIDField(help_text="Network ID")
Expand Down
137 changes: 81 additions & 56 deletions src/api-engine/api/routes/chaincode/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
import os
import zipfile
import tempfile, shutil, tarfile, json

from drf_yasg.utils import swagger_auto_schema
from api.config import FABRIC_CHAINCODE_STORE
Expand All @@ -31,12 +31,43 @@
ChaincodeListResponse
)
from api.common import ok, err
import threading


class ChainCodeViewSet(viewsets.ViewSet):
"""Class represents Channel related operations."""
permission_classes = [IsAuthenticated, ]

def _read_cc_pkg(self, pk, filename, ccpackage_path):
"""
read and extract chaincode package meta info
:pk: chaincode id
:filename: uploaded chaincode package filename
:ccpackage_path: chaincode package path
"""
try:
meta_path = os.path.join(ccpackage_path, "metadata.json")
# extract metadata file
with tarfile.open(os.path.join(ccpackage_path, filename)) as tared_file:
metadata_file = tared_file.getmember("metadata.json")
tared_file.extract(metadata_file, path=ccpackage_path)

with open(meta_path, 'r') as f:
metadata = json.load(f)
language = metadata["type"]
label = metadata["label"]

if os.path.exists(meta_path):
os.remove(meta_path)

chaincode = ChainCode.objects.get(id=pk)
chaincode.language = language
chaincode.label = label
chaincode.save()

except Exception as e:
raise e

@swagger_auto_schema(
query_serializer=PageQuerySerializer,
responses=with_common_response(
Expand Down Expand Up @@ -88,83 +119,77 @@ def list(self, request):
{status.HTTP_201_CREATED: ChainCodeIDSerializer}
),
)
@action(detail=False, methods=['post'])
@action(detail=False, methods=['post'], url_path="chaincodeRepo")
def package(self, request):
serializer = ChainCodePackageBody(data=request.data)
if serializer.is_valid(raise_exception=True):
name = serializer.validated_data.get("name")
version = serializer.validated_data.get("version")
language = serializer.validated_data.get("language")
md5 = serializer.validated_data.get("md5")
file = serializer.validated_data.get("file")
id = make_uuid()

description = serializer.validated_data.get("description")
uuid = make_uuid()
try:
file_path = os.path.join(FABRIC_CHAINCODE_STORE, id)
if not os.path.exists(file_path):
os.makedirs(file_path)
fileziped = os.path.join(file_path, file.name)
with open(fileziped, 'wb') as f:
fd, temp_cc_path = tempfile.mkstemp()
# try to calculate packageid
with open(fd, 'wb') as f:
for chunk in file.chunks():
f.write(chunk)
f.close()
zipped_file = zipfile.ZipFile(fileziped)
for filename in zipped_file.namelist():
zipped_file.extract(filename, file_path)

# When there is go.mod in the chain code, execute the go mod vendor command to obtain dependencies.
chaincode_path = file_path
found = False
for _, dirs, _ in os.walk(file_path):
if found:
break
elif dirs:
for each in dirs:
chaincode_path += "/" + each
if os.path.exists(chaincode_path + "/go.mod"):
cwd = os.getcwd()
print("cwd:", cwd)
os.chdir(chaincode_path)
os.system("go mod vendor")
found = True
os.chdir(cwd)
break
# if can not find go.mod, use the dir after extract zipped_file
if not found:
for _, dirs, _ in os.walk(file_path):
chaincode_path = file_path + "/" + dirs[0]
break

org = request.user.organization
qs = Node.objects.filter(type="peer", organization=org)
if not qs.exists():
raise ResourceNotFound
return Response(
err("at least 1 peer node is required for the chaincode package upload."),
status=status.HTTP_400_BAD_REQUEST
)
peer_node = qs.first()
envs = init_env_vars(peer_node, org)

peer_channel_cli = PeerChainCode("v2.2.0", **envs)
res = peer_channel_cli.lifecycle_package(
name, version, chaincode_path, language)
os.system("rm -rf {}/*".format(file_path))
os.system("mv {}.tar.gz {}".format(name, file_path))
if res != 0:
return Response(err("package chaincode failed."), status=status.HTTP_400_BAD_REQUEST)
return_code, content = peer_channel_cli.lifecycle_calculatepackageid(temp_cc_path)
if (return_code != 0):
return Response(
err("calculate packageid failed for {}.".format(content)),
status=status.HTTP_400_BAD_REQUEST
)
packageid = content.strip()

# check if packageid exists
cc = ChainCode.objects.filter(package_id=packageid)
if cc.exists():
return Response(
err("package with id {} already exists.".format(packageid)),
status=status.HTTP_400_BAD_REQUEST
)

chaincode = ChainCode(
id=id,
name=name,
version=version,
language=language,
id=uuid,
package_id=packageid,
creator=org.name,
md5=md5
description=description,
)
chaincode.save()

# save chaincode package locally
ccpackage_path = os.path.join(FABRIC_CHAINCODE_STORE, packageid)
if not os.path.exists(ccpackage_path):
os.makedirs(ccpackage_path)
ccpackage = os.path.join(ccpackage_path, file.name)
shutil.copy(temp_cc_path, ccpackage)

# start thread to read package meta info, update db
try:
threading.Thread(target=self._read_cc_pkg,
args=(uuid, file.name, ccpackage_path)).start()
except Exception as e:
raise e

return Response(
ok("success"), status=status.HTTP_200_OK
)
except Exception as e:
return Response(
err(e.args), status=status.HTTP_400_BAD_REQUEST
)
return Response(
ok("success"), status=status.HTTP_200_OK
)
finally:
os.remove(temp_cc_path)

@swagger_auto_schema(
method="post",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2817,6 +2817,57 @@
}
},
"response": []
},
{
"name": "Upload Chaincode Package",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
},
{
"key": "Authorization",
"value": "JWT {{token}}",
"type": "text"
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "file",
"type": "file",
"src": "mycc.tar.gz"
},
{
"key": "description",
"value": "CC Description",
"type": "text"
}
]
},
"url": {
"raw": "http://127.0.0.1:8080/api/v1/chaincodes/chaincodeRepo",
"protocol": "http",
"host": [
"127",
"0",
"0",
"1"
],
"port": "8080",
"path": [
"api",
"v1",
"chaincodes",
"chaincodeRepo"
]
}
},
"response": []
}
],
"event": [
Expand Down

0 comments on commit 5c8def2

Please sign in to comment.