Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor old cc pkg upload API #583

Merged
merged 4 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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