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

Add a new org to the channel #490

Merged
merged 13 commits into from
Oct 28, 2022
3 changes: 2 additions & 1 deletion src/api-engine/api/lib/configtxlator/configtxlator.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def proto_decode(self, input, type):
params:
input: A file containing the JSON document.
type: The type of protobuf structure to decode to. For example, 'common.Config'.
output: A file to write the output to.
return:
config
"""
try:
res = run([self.configtxlator,
Expand Down
10 changes: 10 additions & 0 deletions src/api-engine/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,9 +750,19 @@ class Channel(models.Model):
to="Node",
help_text="Orderer list in the channel",
)
config = models.JSONField(
help_text="Channel config",
default=dict,
null=True,
blank=True,
)

def get_channel_config_path(self):
return "/var/www/server/" + self.name + "_config.block"

def get_channel_artifacts_path(self, artifact):
return CELLO_HOME + "/" + self.network.name + "/" + artifact

# class ChainCode(models.Model):
# id = models.UUIDField(
# primary_key=True,
Expand Down
10 changes: 4 additions & 6 deletions src/api-engine/api/routes/channel/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,10 @@ class ChannelIDSerializer(serializers.Serializer):
id = serializers.UUIDField(help_text="Channel ID")


class ChannelUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Channel
fields = (
"name",
)
class ChannelUpdateSerializer(serializers.Serializer):
msp_id = serializers.CharField(
max_length=128, help_text="MSP ID of Organization")
config = serializers.JSONField(help_text="Channel config file")


class ChannelOrgListSerializer(serializers.Serializer):
Expand Down
174 changes: 137 additions & 37 deletions src/api-engine/api/routes/channel/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
#
# SPDX-License-Identifier: Apache-2.0
#
import logging
import json

from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
#

from drf_yasg.utils import swagger_auto_schema

Expand All @@ -13,7 +18,7 @@

from api.config import CELLO_HOME
from api.common.serializers import PageQuerySerializer
from api.utils.common import with_common_response, to_dict
from api.utils.common import with_common_response, parse_block_file, to_dict
from api.auth import TokenAuth
from api.lib.configtxgen import ConfigTX, ConfigTxGen
from api.lib.peer.channel import Channel as PeerChannel
Expand All @@ -38,10 +43,22 @@
FabricNodeType,
)

LOG = logging.getLogger(__name__)

CFG_JSON = "cfg.json"
CFG_PB = "cfg.pb"
DELTA_PB = "delta.pb"
DELTA_JSON = "delta.json"
UPDATED_CFG_JSON = "update_cfg.json"
UPDATED_CFG_PB = "update_cfg.pb"
CFG_DELTA_ENV_JSON = "cfg_delta_env.json"
CFG_DELTA_ENV_PB = "cfg_delta_env.pb"


class ChannelViewSet(viewsets.ViewSet):
"""Class represents Channel related operations."""
authentication_classes = (JSONWebTokenAuthentication, TokenAuth)
parser_classes = [MultiPartParser, FormParser, JSONParser]

@swagger_auto_schema(
query_serializer=PageQuerySerializer,
Expand Down Expand Up @@ -77,7 +94,8 @@ def list(self, request):
}
for channel in channels_pages
]
response = ChannelListResponse({"data": channels_list, "total": channels.count()})
response = ChannelListResponse(
{"data": channels_list, "total": channels.count()})
return Response(data=ok(response.data), status=status.HTTP_200_OK)
except Exception as e:
return Response(
Expand Down Expand Up @@ -107,16 +125,20 @@ def create(self, request):
try:
org = request.user.organization
ConfigTX(org.network.name).createChannel(name, [org.name])
ConfigTxGen(org.network.name).channeltx(profile=name, channelid=name, outputCreateChannelTx="{}.tx".format(name))
tx_path = "{}/{}/channel-artifacts/{}.tx".format(CELLO_HOME, org.network.name, name)
block_path = "{}/{}/channel-artifacts/{}.block".format(CELLO_HOME, org.network.name, name)
ConfigTxGen(org.network.name).channeltx(
profile=name, channelid=name, outputCreateChannelTx="{}.tx".format(name))
tx_path = "{}/{}/channel-artifacts/{}.tx".format(
CELLO_HOME, org.network.name, name)
block_path = "{}/{}/channel-artifacts/{}.block".format(
CELLO_HOME, org.network.name, name)
ordering_node = Node.objects.get(id=orderers[0])
peer_node = Node.objects.get(id=peers[0])
envs = init_env_vars(peer_node, org)
peer_channel_cli = PeerChannel("v2.2.0", **envs)
peer_channel_cli.create(
channel=name,
orderer_url="{}.{}:{}".format(ordering_node.name, org.name.split(".", 1)[1], str(7050)),
orderer_url="{}.{}:{}".format(
ordering_node.name, org.name.split(".", 1)[1], str(7050)),
channel_tx=tx_path,
output_block=block_path
)
Expand Down Expand Up @@ -177,53 +199,130 @@ def update(self, request, pk=None):
"""
serializer = ChannelUpdateSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
peers = serializer.validated_data.get("peers")
msp_id = serializer.validated_data.get("msp_id")
config = serializer.validated_data.get("config")
channel = Channel.objects.get(id=pk)
org = request.user.organization
try:
org = request.user.organization
org_name = org.name
msp_id = org_name.split(".")[0].capitalize()
dir_node = "{}/{}/crypto-config/peerOrganizations".format(
CELLO_HOME, org_name)
peer_cli_envs = {
"CORE_PEER_LOCALMSPID": msp_id,
"CORE_PEER_MSPCONFIGPATH": "{}/{}/users/Admin@{}/msp".format(
dir_node, org_name, org_name),
# Add new org msp
temp_config = channel.config
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need some basic json validation.

temp_config["channel_group"]["groups"]["Application"]["groups"][msp_id] = config
LOG.info("updated_config", temp_config)

# Update and save the config with new org
with open(channel.get_channel_artifacts_path(UPDATED_CFG_JSON), 'w', encoding='utf-8') as f:
LOG.info("channel_updated_config.save.success")
json.dump(temp_config, f, sort_keys=False)

# Encode it into pb.
ConfigTxLator().proto_encode(
input=channel.get_channel_artifacts_path(UPDATED_CFG_JSON),
type="common.Config",
output=channel.get_channel_artifacts_path(UPDATED_CFG_PB),
)

# Calculate the config delta between pb files
ConfigTxLator().compute_update(
original=channel.get_channel_artifacts_path(CFG_PB),
updated=channel.get_channel_artifacts_path(UPDATED_CFG_PB),
channel_id=channel.name,
output=channel.get_channel_artifacts_path(DELTA_PB),
)
# Decode the config delta pb into json
config_update = ConfigTxLator().proto_decode(
input=channel.get_channel_artifacts_path(DELTA_PB),
type="common.ConfigUpdate",
)
# Wrap the config update as envelope
updated_config = {
"payload": {
"header": {
"channel_header": {
"channel_id": channel.name,
"type": 2,
}
},
"data": {
"config_update": to_dict(config_update)
}
}
}
join_peers(peers, peer_cli_envs, dir_node, org, channel.name)
with open(channel.get_channel_artifacts_path(CFG_JSON), 'w', encoding='utf-8') as f:
json.dump(updated_config, f, sort_keys=False)

# Encode the config update envelope into pb
ConfigTxLator().proto_encode(
input=channel.get_channel_artifacts_path(CFG_JSON),
type="common.Envelope",
output=channel.get_channel_artifacts_path(CFG_DELTA_ENV_PB),
)

# Peers to send the update transaction
nodes = Node.objects.filter(
organization=org,
type=FabricNodeType.Peer.name.lower(),
status=NodeStatus.Running.name.lower()
)

for node in nodes:
dir_node = "{}/{}/crypto-config/peerOrganizations".format(
CELLO_HOME, org.name)
env = {
"FABRIC_CFG_PATH": "{}/{}/peers/{}/".format(dir_node, org.name, node.name + "." + org.name),
}
cli = PeerChannel("v2.2.0", **env)
cli.signconfigtx(
channel.get_channel_artifacts_path(CFG_DELTA_ENV_PB))

# Save updated config to db.
channel.config = temp_config
channel.save()
return Response(status=status.HTTP_202_ACCEPTED)
except ObjectDoesNotExist:
raise ResourceNotFound

@swagger_auto_schema(
responses=with_common_response({status.HTTP_200_OK: "Accepted"}),
)
@action(
methods=["get"],
detail=True,
url_path="config"
)
@action(methods=["get"], detail=True, url_path="configs")
def get_channel_org_config(self, request, pk=None):
try:
org = request.user.organization
channel = Channel.objects.get(id=pk)
path = channel.get_channel_config_path()
node = Node.objects.filter(
organization=org,
type=FabricNodeType.Peer.name.lower(),
status=NodeStatus.Running.name.lower()
).first()
dir_node = "{}/{}/crypto-config/peerOrganizations".format(
CELLO_HOME, org.name)
env = {
"FABRIC_CFG_PATH": "{}/{}/peers/{}/".format(dir_node, org.name, node.name + "." + org.name),
}
peer_channel_cli = PeerChannel("v2.2.0", **env)
peer_channel_cli.fetch(option="config", channel=channel.name)
config = ConfigTxLator().proto_decode(input=path, type="common.Block")
if channel.config:
return Response(data=channel.config, status=status.HTTP_200_OK)
else:
node = Node.objects.filter(
organization=org,
type=FabricNodeType.Peer.name.lower(),
status=NodeStatus.Running.name.lower()
).first()
dir_node = "{}/{}/crypto-config/peerOrganizations".format(
CELLO_HOME, org.name)
env = {
"FABRIC_CFG_PATH": "{}/{}/peers/{}/".format(dir_node, org.name, node.name + "." + org.name),
}
peer_channel_cli = PeerChannel("v2.2.0", **env)
peer_channel_cli.fetch(option="config", channel=channel.name)

# Decode latest config block into json
config = ConfigTxLator().proto_decode(input=path, type="common.Block")
config = parse_block_file(config)
# Save as a json file for future usage
with open(channel.get_channel_artifacts_path(CFG_JSON), 'w', encoding='utf-8') as f:
json.dump(config, f, sort_keys=False)
# Encode block file as pb
ConfigTxLator().proto_encode(
input=channel.get_channel_artifacts_path(CFG_JSON),
type="common.Config",
output=channel.get_channel_artifacts_path(CFG_PB),
)
channel.config = config
channel.save()
zhuyuanmao marked this conversation as resolved.
Show resolved Hide resolved
return Response(data=config, status=status.HTTP_200_OK)
except ObjectDoesNotExist:
raise ResourceNotFound
return Response(data=to_dict(config, org.name.split(".")[0].capitalize()), status=status.HTTP_200_OK)


def init_env_vars(node, org):
Expand All @@ -242,7 +341,8 @@ def init_env_vars(node, org):

envs = {
"CORE_PEER_TLS_ENABLED": "true",
"CORE_PEER_LOCALMSPID": "{}MSP".format(org_name.capitalize()), # "Org1.cello.comMSP"
# "Org1.cello.comMSP"
"CORE_PEER_LOCALMSPID": "{}MSP".format(org_name.capitalize()),
"CORE_PEER_TLS_ROOTCERT_FILE": "{}/{}/peers/{}/tls/ca.crt".format(dir_node, org_name, node.name + "." + org_name),
"CORE_PEER_ADDRESS": "{}:{}".format(
node.name + "." + org_name, str(7051)),
Expand Down
30 changes: 30 additions & 0 deletions src/api-engine/api/routes/node/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from api.lib.agent import AgentHandler
from api.utils.port_picker import set_ports_mapping, find_available_ports
from api.common import ok, err
from api.routes.channel.views import init_env_vars, join_peers

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -748,6 +749,35 @@ def node_config(self, request, pk=None):
except Exception as e:
raise e

@action(methods=["post"], detail=True, url_path="block", url_name="block")
def block_file(self, request, pk=None):
'''
Peer join channel by uploading a genesis block file
'''
try:
self._validate_organization(request)
organization = request.user.organization
org = organization.name
try:
node = Node.objects.get(
id=pk, organization=organization
)
except ObjectDoesNotExist:
raise ResourceNotFound
envs = init_env_vars(node, organization)
block_path = "{}/{}/crypto-config/peerOrganizations/{}/peers/{}/{}.block" \
.format(CELLO_HOME, org, org, node.name + "." + org, "channel")
uploaded_block_file = request.data['file']
with open(block_path, 'wb+') as f:
for chunk in uploaded_block_file.chunks():
f.write(chunk)
join_peers(envs, block_path)
return Response(status=status.HTTP_202_ACCEPTED)
except Exception as e:
return Response(
err(e.args), status=status.HTTP_400_BAD_REQUEST
)

def _register_user(self, request, pk=None):
serializer = NodeUserCreateSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
Expand Down
18 changes: 9 additions & 9 deletions src/api-engine/api/utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ def zip_dir(dirpath, outFullName):
for path, dirnames, filenames in os.walk(dirpath):
fpath = dir_dst + path.replace(dirpath, '')
for filename in filenames:
zdir.write(os.path.join(path, filename), os.path.join(fpath, filename))
zdir.write(os.path.join(path, filename),
os.path.join(fpath, filename))
zdir.close()


Expand All @@ -132,7 +133,7 @@ def zip_file(dirpath, outFullName):
zfile.close()


def to_dict(data, org_name):
def parse_block_file(data):
"""
Parse org config from channel config block.

Expand All @@ -141,11 +142,10 @@ def to_dict(data, org_name):
:return organization config
"""
config = loads(data)
if config.get("data") is not None:
payloads = config["data"]["data"]
for p in payloads:
groups = p["payload"]["data"]["config"]["channel_group"]["groups"]["Application"]["groups"]
res = groups.get(org_name, None)
if res is not None:
return res
if config.get("data"):
return config.get("data").get("data")[0].get("payload").get("data").get("config")
return {"error": "can't find channel config"}


def to_dict(data):
return loads(data)
3 changes: 2 additions & 1 deletion src/dashboard/src/pages/Operator/Node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ const CreateNode = props => {
};

const onFinish = values => {
handleCreate(values, createCallback);
const msg = { ...values, num: parseInt(values.num, 10) };
handleCreate(msg, createCallback);
};

const formItemLayout = {
Expand Down
Loading