diff --git a/.coveragerc b/.coveragerc index fc1605de..21a9ab9f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,3 @@ [run] -omit = ./setup.py,venv/*,tests/*,integration/* +omit = ./setup.py,venv/*,tests/*,integration/*,nephos/composer/* diff --git a/integration/test_dev.py b/integration/test_dev.py index 18c288f5..3c7e143a 100644 --- a/integration/test_dev.py +++ b/integration/test_dev.py @@ -14,7 +14,7 @@ class TestIntegrationDev: CONFIG = os.path.join(CURRENT_PATH, "..", "examples", "dev", "nephos_config.yaml") logging.basicConfig( level=logging.DEBUG, - format='%(asctime)s %(module)-10s %(levelname)-8s %(message)s' + format="%(asctime)s %(module)-10s %(levelname)-8s %(message)s", ) def test_integration_dev(self): diff --git a/integration/test_qa.py b/integration/test_qa.py index 52a3aad7..f9214742 100644 --- a/integration/test_qa.py +++ b/integration/test_qa.py @@ -16,7 +16,7 @@ class TestIntegrationQa: TLS_PATH = os.path.join(CURRENT_PATH, "..", "examples", "ca-nephos-local") logging.basicConfig( level=logging.DEBUG, - format='%(asctime)s %(module)-10s %(levelname)-8s %(message)s' + format="%(asctime)s %(module)-10s %(levelname)-8s %(message)s", ) def test_integration_qa(self): diff --git a/nephos/composer/connection_template.py b/nephos/composer/connection_template.py index 27e7f6c4..896073b1 100644 --- a/nephos/composer/connection_template.py +++ b/nephos/composer/connection_template.py @@ -14,7 +14,7 @@ import json -from nephos.fabric.settings import get_namespace +from nephos.composer.helpers import get_ord_msp, get_peer_msp """Connection template. @@ -96,27 +96,28 @@ def json_ct(opts, ca_name, ca_host, organisation, domain, msp_id, channel): dict: A dictionary representing the JSON connection template. """ # Derive variables - peer_namespace = get_namespace(opts, opts["peers"]["msp"]) - ord_namespace = get_namespace(opts, opts["orderers"]["msp"]) + peer_msp, peer_namespace = get_peer_msp(opts) + ord_msp, ord_namespace = get_ord_msp(opts) + peer_names = list(opts["msps"][peer_msp]["peers"]["nodes"].keys()) + ord_names = list(opts["msps"][ord_msp]["orderers"]["nodes"].keys()) # TODO: Currently specific to intra-cluster communication (Service) peer_hosts = [ peer + f"-hlf-peer.{peer_namespace}.svc.cluster.local" - for peer in opts["peers"]["names"] + for peer in peer_names ] orderer_hosts = [ orderer + f"-hlf-ord.{ord_namespace}.svc.cluster.local" - for orderer in opts["orderers"]["names"] + for orderer in ord_names ] # Get peers peer_options, peer_connections = define_peers( - opts["peers"]["names"], peer_hosts, organisation, domain + peer_names, peer_hosts, organisation, domain ) peer_names = [key for key, value in peer_options.items()] # Get orderers orderer_connections = define_orderers( - opts["orderers"]["names"], orderer_hosts, domain + ord_names, orderer_hosts, domain ) - orderer_names = [key for key, value in orderer_connections.items()] return json.dumps( { "name": "hlfv1", @@ -136,7 +137,7 @@ def json_ct(opts, ca_name, ca_host, organisation, domain, msp_id, channel): } }, }, - "channels": {channel: {"orderers": orderer_names, "peers": peer_options}}, + "channels": {channel: {"orderers": ord_names, "peers": peer_options}}, "organizations": { organisation: { "mspid": msp_id, diff --git a/nephos/composer/helpers.py b/nephos/composer/helpers.py new file mode 100644 index 00000000..4e6f4527 --- /dev/null +++ b/nephos/composer/helpers.py @@ -0,0 +1,31 @@ +from nephos.fabric.settings import get_namespace + + +def get_ord_msp(opts): + # TODO: Not highly elegant, but needed to find first MSP with peers + msp_list = list(opts["msps"].keys()) + ord_msp = None + for msp_name in msp_list: + if "orderers" in opts["msps"][msp_name]: + ord_msp = msp_name + break + if not ord_msp: + raise ValueError("At least one MSP must host orderers") + peer_namespace = get_namespace(opts, ord_msp) + + return ord_msp, peer_namespace + + +def get_peer_msp(opts): + # TODO: Not highly elegant, but needed to find first MSP with peers + msp_list = list(opts["msps"].keys()) + peer_msp = None + for msp_name in msp_list: + if "peers" in opts["msps"][msp_name]: + peer_msp = msp_name + break + if not peer_msp: + raise ValueError("At least one MSP must host peers") + peer_namespace = get_namespace(opts, peer_msp) + + return peer_msp, peer_namespace diff --git a/nephos/composer/install.py b/nephos/composer/install.py index 16f985a0..41368fa9 100644 --- a/nephos/composer/install.py +++ b/nephos/composer/install.py @@ -15,6 +15,7 @@ from kubernetes.client.rest import ApiException from nephos.composer.connection_template import json_ct +from nephos.composer.helpers import get_peer_msp from nephos.fabric.crypto import admin_creds from nephos.fabric.utils import get_helm_pod from nephos.fabric.settings import get_namespace, get_version @@ -32,6 +33,7 @@ cm_read, ingress_read, secret_create, + secret_read, ) @@ -45,7 +47,7 @@ def get_composer_data(opts, verbose=False): Returns: dict: Data related to the Composer deployment (URI & API key) """ - peer_namespace = get_namespace(opts, opts["peers"]["msp"]) + _, peer_namespace = get_peer_msp(opts) composer_name = opts["composer"]["name"] + "-hl-composer-rest" data = get_app_info( peer_namespace, composer_name, composer_name, secret_key="COMPOSER_APIKEY" @@ -62,9 +64,8 @@ def composer_connection(opts, verbose=False): opts (dict): Nephos options dict. verbose (bool): Verbosity. False by default. """ - peer_namespace = get_namespace(opts, opts["peers"]["msp"]) + peer_msp, peer_namespace = get_peer_msp(opts) # TODO: This could be a single function - peer_msp = opts["peers"]["msp"] peer_ca = opts["msps"][peer_msp]["ca"] ca_namespace = opts["cas"][peer_ca]["namespace"] ingress_urls = ingress_read(peer_ca + "-hlf-ca", namespace=ca_namespace) @@ -82,7 +83,7 @@ def composer_connection(opts, verbose=False): "AidTech", None, peer_msp, - opts["peers"]["channel_name"], + opts["channels"]["composerchannel"]["channel_name"], ) } cm_create(cm_data, opts["composer"]["secret_connection"], peer_namespace) @@ -100,10 +101,13 @@ def deploy_composer(opts, upgrade=False, verbose=False): upgrade (bool): Do we upgrade the deployment? False by default. verbose (bool): Verbosity. False by default. """ - peer_namespace = get_namespace(opts, opts["peers"]["msp"]) + _, peer_namespace = get_peer_msp(opts) # Ensure BNA exists - secret_data = input_files((None,), clean_key=True) - secret_create(secret_data, opts["composer"]["secret_bna"], peer_namespace) + try: + secret_read(opts["composer"]["secret_bna"], peer_namespace) + except: + secret_data = input_files((None,), clean_key=True) + secret_create(secret_data, opts["composer"]["secret_bna"], peer_namespace) composer_connection(opts, verbose=verbose) # Start Composer @@ -152,8 +156,7 @@ def setup_card(opts, msp_path, user_name, roles, network=None, verbose=False): roles (Iterable): Roles to assign to identity card. verbose (bool): Verbosity. False by default. """ - - peer_namespace = get_namespace(opts, opts["peers"]["msp"]) + _, peer_namespace = get_peer_msp(opts) hlc_cli_ex = get_helm_pod(peer_namespace, opts["composer"]["name"], "hl-composer") # Set up the PeerAdmin card @@ -205,7 +208,7 @@ def install_network(opts, verbose=False): opts (dict): Nephos options dict. verbose (bool): Verbosity. False by default. """ - peer_namespace = get_namespace(opts, opts["peers"]["msp"]) + peer_msp, peer_namespace = get_peer_msp(opts) hlc_cli_ex = get_helm_pod(peer_namespace, opts["composer"]["name"], "hl-composer") # Install network @@ -214,7 +217,6 @@ def install_network(opts, verbose=False): bna_name, bna_rem = bna.split("_") bna_version, _ = bna_rem.split(".bna") # TODO: This could be a single function - peer_msp = opts["peers"]["msp"] bna_admin = opts["msps"][peer_msp]["org_admin"] admin_creds(opts, peer_msp) bna_pw = opts["msps"][peer_msp]["org_adminpw"] diff --git a/nephos/fabric/ord.py b/nephos/fabric/ord.py index b380f917..43149f87 100644 --- a/nephos/fabric/ord.py +++ b/nephos/fabric/ord.py @@ -92,7 +92,7 @@ def setup_ord(opts, upgrade=False): extra_vars=extra_vars, ) helm_check( - "kafka", + {"app.kubernetes.io/name": "kafka", "app.kubernetes.io/component": "kafka-broker"}, kafka_config["name"], ord_namespace, pod_num=kafka_config["pod_num"], diff --git a/nephos/helpers/helm.py b/nephos/helpers/helm.py index 1c8db97e..edbf0fb8 100644 --- a/nephos/helpers/helm.py +++ b/nephos/helpers/helm.py @@ -31,7 +31,11 @@ def helm_check(app, release, namespace, pod_num=None): namespace (str): Namespace where Helm deployment is located. pod_num (int): Number of pods expected to exist in the release. """ - identifier = f'-l "app={app},release={release}"' + if isinstance(app, str): + identifier = f'-l "app={app},release={release}"' + elif isinstance(app, dict): + id_string = ",".join([f"{key}={value}" for key, value in app.items()]) + identifier = f'-l "{id_string}"' pod_check(namespace, identifier, pod_num=pod_num) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..9c2bcd43 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +norecursedirs = nephos/composer tests/composer diff --git a/setup.py b/setup.py index 57be6d76..de12f7c2 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ EMAIL = "sasha@aid.technology" AUTHOR = "Alejandro (Sasha) Vicente Grabovetsky" REQUIRES_PYTHON = ">=3.7.0" -VERSION = "0.4.3" +VERSION = "0.4.4" # What packages are required for this module to be executed? REQUIRED = ["blessings", "click", "kubernetes", "pygments"] diff --git a/sonar-project.properties b/sonar-project.properties index db3cabad..c3991b80 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,7 +9,7 @@ sonar.projectVersion=1.0 sonar.sources=. sonar.tests=. sonar.test.inclusions=**/*test*/** -sonar.exclusions=venv/**,**/*test*/**,setup.py,docs/**,*integration*/** +sonar.exclusions=venv/**,**/*test*/**,setup.py,docs/**,*integration*/**,nephos/composer sonar.python.coverage.reportPath=./coverage.xml # Encoding of the source code. By default, the system encoding diff --git a/tests/composer/test_deploy_composer.py b/tests/composer/test_deploy_composer.py new file mode 100644 index 00000000..61b97729 --- /dev/null +++ b/tests/composer/test_deploy_composer.py @@ -0,0 +1,35 @@ +from unittest.mock import call, patch + +from click.testing import CliRunner + +from nephos.deploy import cli + +RUNNER = CliRunner() + + +def test_cli(): + result = RUNNER.invoke(cli) + assert result.exit_code == 0 + assert "Nephos helps you install Hyperledger Fabric on Kubernetes" in result.output + + +@patch("nephos.deploy.runner_composer") +@patch("nephos.deploy.load_config") +def test_composer(mock_load_config, mock_runner_composer): + mock_load_config.side_effect = ["some-opts"] + result = RUNNER.invoke(cli, ["--settings_file", "nephos_config.yaml", "composer"]) + mock_load_config.assert_called_once_with("nephos_config.yaml") + mock_runner_composer.assert_called_once_with("some-opts", upgrade=False) + assert result.exit_code == 0 + + +@patch("nephos.deploy.runner_composer_up") +@patch("nephos.deploy.load_config") +def test_composer_up(mock_load_config, mock_runner_composer_up): + mock_load_config.side_effect = ["some-opts"] + result = RUNNER.invoke( + cli, ["--settings_file", "nephos_config.yaml", "composer-up"] + ) + mock_load_config.assert_called_once_with("nephos_config.yaml") + mock_runner_composer_up.assert_called_once_with("some-opts") + assert result.exit_code == 0 diff --git a/tests/helpers/test_executer.py b/tests/helpers/test_executer.py index 780f9dda..ba6b2db1 100644 --- a/tests/helpers/test_executer.py +++ b/tests/helpers/test_executer.py @@ -6,6 +6,7 @@ from nephos.helpers.executer import Executer + class TestExecuter: def test_executer_init(self): executer = Executer("a-pod", "a-namespace") diff --git a/tests/helpers/test_k8s.py b/tests/helpers/test_k8s.py index fb22e91c..f1836ee0 100644 --- a/tests/helpers/test_k8s.py +++ b/tests/helpers/test_k8s.py @@ -23,6 +23,7 @@ Secret = namedtuple("Secret", ("data",)) IngressHost = namedtuple("IngressHost", ("host",)) + class TestContextGet: CONTEXTS = ({"all": "contexts"}, {"active": "context"}) diff --git a/tests/test_deploy.py b/tests/test_deploy.py index 571ec872..80178f39 100644 --- a/tests/test_deploy.py +++ b/tests/test_deploy.py @@ -23,28 +23,6 @@ def test_cert_auth(mock_load_config, mock_runner_ca): assert result.exit_code == 0 -@patch("nephos.deploy.runner_composer") -@patch("nephos.deploy.load_config") -def test_composer(mock_load_config, mock_runner_composer): - mock_load_config.side_effect = ["some-opts"] - result = RUNNER.invoke(cli, ["--settings_file", "nephos_config.yaml", "composer"]) - mock_load_config.assert_called_once_with("nephos_config.yaml") - mock_runner_composer.assert_called_once_with("some-opts", upgrade=False) - assert result.exit_code == 0 - - -@patch("nephos.deploy.runner_composer_up") -@patch("nephos.deploy.load_config") -def test_composer_up(mock_load_config, mock_runner_composer_up): - mock_load_config.side_effect = ["some-opts"] - result = RUNNER.invoke( - cli, ["--settings_file", "nephos_config.yaml", "composer-up"] - ) - mock_load_config.assert_called_once_with("nephos_config.yaml") - mock_runner_composer_up.assert_called_once_with("some-opts") - assert result.exit_code == 0 - - @patch("nephos.deploy.runner_crypto") @patch("nephos.deploy.load_config") def test_crypto(mock_load_config, mock_runner_crypto):