From 11476868d335bfae2b72aa287410b1985d6adbe8 Mon Sep 17 00:00:00 2001 From: Haitao Yue Date: Wed, 10 Jul 2019 13:22:58 +0800 Subject: [PATCH] [CE-632] Support param customize for fabric ca server For fabric ca server deployment, can set parameters such as, admin_name,admin_password,hosts. Add volumes support in k8s agent. Change-Id: Ie4959ce8e386dfdd281df2c049d77d0ee255208a Signed-off-by: Haitao Yue --- .pre-commit-config.yaml | 2 +- .../src/network/fabric/__init__.py | 28 ++++++++++++++++-- .../kubernetes-agent/src/utils/client.py | 29 ++++++++++++++++++- src/api-engine/api/models.py | 22 ++++++++++++++ src/api-engine/api/routes/node/serializers.py | 19 +++++++++++- src/api-engine/api/routes/node/views.py | 24 +++++++++++---- src/api-engine/api/tasks/agent.py | 14 +++++++++ 7 files changed, 127 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a2180611..ed9355e80 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,5 +3,5 @@ repos: rev: stable hooks: - id: black - entry: black -l 79 src/api-engine + entry: black -l 79 src/api-engine src/agent/kubernetes-agent language_version: python3.7 diff --git a/src/agent/kubernetes-agent/src/network/fabric/__init__.py b/src/agent/kubernetes-agent/src/network/fabric/__init__.py index 2818fbfd3..23adb424c 100644 --- a/src/agent/kubernetes-agent/src/network/fabric/__init__.py +++ b/src/agent/kubernetes-agent/src/network/fabric/__init__.py @@ -2,10 +2,20 @@ # SPDX-License-Identifier: Apache-2.0 # import logging +import os +import json from enum import Enum, unique LOG = logging.getLogger(__name__) +CA_CONFIG = json.loads(os.getenv("CA_CONFIG", "{}")) +# Initial admin name/password for ca server +CA_ADMIN_NAME = CA_CONFIG.get("admin_name", "admin") +CA_ADMIN_PASSWORD = CA_CONFIG.get("admin_password", "adminpw") +CA_HOSTS = CA_CONFIG.get("hosts", "").split(",") +AGENT_IP = os.getenv("AGENT_IP", "") +# Set fabric ca hosts from agent ip and user customize hosts. +CA_HOSTS.append(AGENT_IP) @unique @@ -35,11 +45,25 @@ def _generate_deployment(self): { "name": "FABRIC_CA_HOME", "value": "/etc/hyperledger/fabric-ca-server", - } + }, + { + "name": "FABRIC_CA_SERVER_HOME", + "value": "/etc/hyperledger/fabric-ca-server/crypto", + }, + {"name": "FABRIC_CA_SERVER_TLS_ENABLED", "value": "true"}, + { + "name": "FABRIC_CA_SERVER_CSR_HOSTS", + "value": ",".join(CA_HOSTS), + }, ] ports = [7054] command = ["fabric-ca-server"] - command_args = ["start", "-b", "admin:adminpw", "-d"] + command_args = [ + "start", + "-b", + "%s:%s" % (CA_ADMIN_NAME, CA_ADMIN_PASSWORD), + "-d", + ] containers.append( { "image": image, diff --git a/src/agent/kubernetes-agent/src/utils/client.py b/src/agent/kubernetes-agent/src/utils/client.py index ffd189708..469d831a6 100644 --- a/src/agent/kubernetes-agent/src/utils/client.py +++ b/src/agent/kubernetes-agent/src/utils/client.py @@ -46,10 +46,28 @@ def get_or_create_namespace(self, name=None): def create_deployment(self, namespace=None, *args, **kwargs): containers = kwargs.get("containers", []) + volumes_json = kwargs.get("volumes", []) deploy_name = kwargs.get("name") labels = kwargs.get("labels", {}) labels.update({"app": deploy_name}) container_pods = [] + volumes = [] + for volume in volumes_json: + volume_name = volume.get("name") + host_path = volume.get("host_path", None) + parameters = {} + if host_path: + host_path = client.V1HostPathVolumeSource(path=host_path) + parameters.update({"host_path": host_path}) + persistent_volume_claim = volume.get("pvc", None) + if persistent_volume_claim: + persistent_volume_claim = client.V1PersistentVolumeClaimVolumeSource( + claim_name=persistent_volume_claim + ) + parameters.update( + {"persistent_volume_claim": persistent_volume_claim} + ) + volumes.append(client.V1Volume(name=volume_name, **parameters)) for container in containers: name = container.get("name") image = container.get("image") @@ -57,6 +75,14 @@ def create_deployment(self, namespace=None, *args, **kwargs): environments = container.get("environments", []) command = container.get("command", []) command_args = container.get("command_args", []) + volume_mounts = container.get("volume_mounts", []) + volume_mounts = [ + client.V1VolumeMount( + mount_path=volume_mount.get("path"), + name=volume_mount.get("name"), + ) + for volume_mount in volume_mounts + ] environments = [ client.V1EnvVar(name=env.get("name"), value=env.get("value")) @@ -74,10 +100,11 @@ def create_deployment(self, namespace=None, *args, **kwargs): args=command_args, ports=ports, image_pull_policy="IfNotPresent", + volume_mounts=volume_mounts, ) ) deployment_metadata = client.V1ObjectMeta(name=deploy_name) - pod_spec = client.V1PodSpec(containers=container_pods) + pod_spec = client.V1PodSpec(containers=container_pods, volumes=volumes) spec_metadata = client.V1ObjectMeta(labels=labels) template_spec = client.V1PodTemplateSpec( metadata=spec_metadata, spec=pod_spec diff --git a/src/api-engine/api/models.py b/src/api-engine/api/models.py index a28db1fc3..3521482cd 100644 --- a/src/api-engine/api/models.py +++ b/src/api-engine/api/models.py @@ -309,6 +309,22 @@ def get_compose_file_path(instance, file): ) +class FabricCA(models.Model): + admin_name = models.CharField( + help_text="Admin username for ca server", + default="admin", + max_length=32, + ) + admin_password = models.CharField( + help_text="Admin password for ca server", + default="adminpw", + max_length=32, + ) + hosts = JSONField( + help_text="Hosts for ca", null=True, blank=True, default=list + ) + + class Node(models.Model): id = models.UUIDField( primary_key=True, @@ -340,6 +356,12 @@ class Node(models.Model): % (FabricNodeType.names()), max_length=64, ) + ca = models.ForeignKey( + FabricCA, + help_text="CA configuration of node", + null=True, + on_delete=models.CASCADE, + ) urls = JSONField( help_text="URL configurations for node", null=True, diff --git a/src/api-engine/api/routes/node/serializers.py b/src/api-engine/api/routes/node/serializers.py index 3a294510a..a9343bb5d 100644 --- a/src/api-engine/api/routes/node/serializers.py +++ b/src/api-engine/api/routes/node/serializers.py @@ -12,7 +12,7 @@ HostType, ) from api.common.serializers import PageQuerySerializer -from api.models import Node, Port +from api.models import Node, Port, FabricCA LOG = logging.getLogger(__name__) @@ -76,12 +76,28 @@ class NodeListSerializer(serializers.Serializer): ) +class FabricCASerializer(serializers.ModelSerializer): + hosts = serializers.ListField( + help_text="Hosts for ca support", + child=serializers.CharField(help_text="Host name", max_length=64), + required=False, + allow_empty=True, + ) + + class Meta: + model = FabricCA + fields = ("admin_name", "admin_password", "hosts") + + class NodeCreateBody(serializers.ModelSerializer): agent_type = serializers.ChoiceField( help_text="Agent type", choices=HostType.to_choices(True), required=False, ) + ca = FabricCASerializer( + help_text="CA configuration for node", required=False + ) class Meta: model = Node @@ -91,6 +107,7 @@ class Meta: "type", "agent_type", "agent", + "ca", ) extra_kwargs = { "network_type": {"required": True}, diff --git a/src/api-engine/api/routes/node/views.py b/src/api-engine/api/routes/node/views.py index 521eba081..387749df9 100644 --- a/src/api-engine/api/routes/node/views.py +++ b/src/api-engine/api/routes/node/views.py @@ -19,7 +19,7 @@ from api.common.enums import NodeStatus from api.exceptions import CustomError, NoResource from api.exceptions import ResourceNotFound -from api.models import Agent, Node, Port +from api.models import Agent, Node, Port, FabricCA, FabricNodeType from api.routes.node.serializers import ( NodeOperationSerializer, NodeQuery, @@ -127,6 +127,7 @@ def create(self, request): network_version = serializer.validated_data.get("network_version") agent = serializer.validated_data.get("agent") node_type = serializer.validated_data.get("type") + ca = serializer.validated_data.get("ca", {}) if agent is None: available_agents = ( Agent.objects.annotate(network_num=Count("node__network")) @@ -150,6 +151,19 @@ def create(self, request): node_count = Node.objects.filter(agent=agent).count() if node_count >= agent.node_capacity or not agent.schedulable: raise NoResource + + fabric_ca = None + if node_type == FabricNodeType.Ca.name.lower(): + ca_body = {} + admin_name = ca.get("admin_name") + admin_password = ca.get("admin_password") + hosts = ca.get("hosts", []) + if admin_name: + ca_body.update({"admin_name": admin_name}) + if admin_password: + ca_body.update({"admin_password": admin_password}) + fabric_ca = FabricCA(**ca_body, hosts=hosts) + fabric_ca.save() node = Node( network_type=network_type, agent=agent, @@ -157,6 +171,7 @@ def create(self, request): user=request.user, organization=request.user.organization, type=node_type, + ca=fabric_ca, ) node.save() agent_config_file = ( @@ -172,11 +187,8 @@ def create(self, request): agent_config_file=agent_config_file, node_update_api=node_update_api, ) - response = NodeIDSerializer(data={"id": str(node.id)}) - if response.is_valid(raise_exception=True): - return Response( - response.validated_data, status=status.HTTP_201_CREATED - ) + response = NodeIDSerializer({"id": str(node.id)}) + return Response(response.data, status=status.HTTP_201_CREATED) @swagger_auto_schema( methods=["post"], diff --git a/src/api-engine/api/tasks/agent.py b/src/api-engine/api/tasks/agent.py index f4c6eb544..95f5484d8 100644 --- a/src/api-engine/api/tasks/agent.py +++ b/src/api-engine/api/tasks/agent.py @@ -3,6 +3,7 @@ # from __future__ import absolute_import, unicode_literals +import json import logging import os @@ -34,12 +35,25 @@ def create_node(self, node_id=None, agent_image=None, **kwargs): "NODE_TYPE": node.type, "NODE_ID": str(node.id), "AGENT_ID": str(node.agent.id), + "AGENT_IP": str(node.agent.ip), "AGENT_CONFIG_FILE": agent_config_file, "NODE_UPDATE_URL": node_update_api, # Token for call update node api "TOKEN": ADMIN_TOKEN, "OPERATION": AgentOperation.Start.value, } + if node.ca: + environment.update( + { + "CA_CONFIG": json.dumps( + { + "admin_name": node.ca.admin_name, + "admin_password": node.ca.admin_password, + "hosts": ",".join(node.ca.hosts), + } + ) + } + ) client = docker.from_env() client.containers.run( agent_image, auto_remove=True, environment=environment, detach=True