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

Kind multinode opt mount #344

Merged
merged 5 commits into from
Jun 26, 2020
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
1 change: 1 addition & 0 deletions helm/configurations/values-dev.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# REANA components pointing to `latest`, locally built master branch

diegodelemos marked this conversation as resolved.
Show resolved Hide resolved
components:
reana_server:
image: reanahub/reana-server
Expand Down
1 change: 1 addition & 0 deletions helm/reana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This Helm automatically prefixes all names using the release name to avoid colli
| `components.reana_workflow_controller.environment` | REANA-Workflow-Controller environment variables | `{SHARED_VOLUME_PATH: /var/reana}` |
| `components.reana_workflow_controller.image` | | `reanahub/reana-workflow-controller:<chart-release-verion>` |
| `components.reana_workflow_controller.imagePullPolicy` | REANA-Workflow-Controller image pull policy | IfNotPresent |
| `components.reana_workflow_controller.environment.REANA_JOB_HOSTPATH_MOUNTS` | JSON list of optional hostPath mounts, for all user jobs. Each mount object has a key `name` (name of the mount), `hostPath` (path to the directory to be mounted from the Kubernetes nodes) and `mountPath` (path inside the job containers where the `hostPath` will be mount) | None |
| `components.reana_workflow_engine_cwl.image` | [REANA-Workflow-Engine-CWL](https://hub.docker.com/repository/docker/reanahub/reana-workflow-engine-cwl) image to use | `reanahub/reana-workflow-engine-cwl:<chart-release-verion>` |
| `components.reana_workflow_engine_serial.image` | [REANA-Workflow-Engine-Serial](https://hub.docker.com/repository/docker/reanahub/reana-workflow-engine-serial) image to use | `reanahub/reana-workflow-engine-serial:<chart-release-verion>` |
| `components.reana_workflow_engine_yadage.image` | [REANA-Workflow-Engine-Yadage](https://hub.docker.com/repository/docker/reanahub/reana-workflow-engine-yadage) image to use | `reanahub/reana-workflow-engine-yadage:<chart-release-verion>` |
Expand Down
2 changes: 2 additions & 0 deletions helm/reana/templates/serviceaccount.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.default_runtime_namespace }}
annotations:
scheduler.alpha.kubernetes.io/node-selector: reana-system=runtime
---
apiVersion: v1
kind: ServiceAccount
Expand Down
245 changes: 179 additions & 66 deletions reana/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
import sys
import time
import traceback
import yaml

import click

from reana.k8s_utils import (
exec_into_component,
get_external_url,
get_prefixed_component_name,
)

Expand Down Expand Up @@ -731,6 +731,27 @@ def get_current_version(component, dirty=False):
return tag[1:]


def volume_mounts_to_list(ctx, param, value):
"""Convert tuple params to dictionary. e.g `(foo:bar)` to `{'foo': 'bar'}`.

:param options: A tuple with CLI options.
:returns: A list with all parsed mounts.
"""
try:
return [
{"hostPath": op.split(":")[0], "containerPath": op.split(":")[1]}
for op in value
]
except ValueError:
click.secho(
'[ERROR] Option "{0}" is not valid. '
'It must follow format "param=value".'.format(" ".join(value)),
err=True,
fg="red",
),
sys.exit(1)


@cli.command()
def version():
"""Show version."""
Expand Down Expand Up @@ -1636,23 +1657,96 @@ def client_uninstall(): # noqa: D301
run_command("pip check")


@click.option(
"-d",
"--debug",
is_flag=True,
default=False,
help="Should we build REANA in debug mode? ",
)
@cli.command(name="cluster-build")
def cluster_build(): # noqa: D301
def cluster_build(debug): # noqa: D301
"""Build REANA cluster."""
for cmd in [
"reana-dev git-submodule --update",
"reana-dev docker-build --exclude-components r-ui,r-a-vomsproxy",
]:
cmds = ["reana-dev git-submodule --update"]
if debug:
cmds.append("reana-dev python-install-eggs")
cmds.append(
"reana-dev docker-build --exclude-components r-ui,r-a-vomsproxy"
+ (" -b DEBUG=1" if debug else "")
)
for cmd in cmds:
run_command(cmd, "reana")


@cli.command(name="cluster-deploy")
def cluster_deploy(): # noqa: D301
@click.option(
"--namespace", "-n", default="default", help="Kubernetes namespace [default]"
)
@click.option(
"-j",
"--job-mounts",
multiple=True,
callback=volume_mounts_to_list,
help="Which directories from the Kubernetes nodes to mount inside the job pods? "
"cluster_node_path:job_pod_mountpath, e.g /var/reana/mydata:/mydata",
)
@click.option(
"-d",
"--debug",
is_flag=True,
default=False,
help="Should we deploy REANA in debug mode? ",
)
def cluster_deploy(namespace, job_mounts, debug): # noqa: D301
"""Deploy REANA cluster."""
for cmd in [
"helm install reana helm/reana -f helm/configurations/values-dev.yaml -n default --wait",

def job_mounts_to_config(job_mounts):
job_mount_list = []
for mount in job_mounts:
job_mount_list.append(
{
"name": mount["containerPath"].replace("/", "-")[1:],
"hostPath": mount["hostPath"],
"mountPath": mount["containerPath"],
}
)

job_mount_config = ""
if job_mount_list:
job_mount_config = json.dumps(job_mount_list)
else:
job_mount_config = ""
diegodelemos marked this conversation as resolved.
Show resolved Hide resolved

return job_mount_config

values_yaml = "helm/configurations/values-dev.yaml"
values_dict = {}
with open(os.path.join(get_srcdir("reana"), values_yaml)) as f:
values_dict = yaml.safe_load(f.read())

job_mount_config = job_mounts_to_config(job_mounts)
if job_mount_config:
values_dict.setdefault("components", {}).setdefault(
"reana_workflow_controller", {}
).setdefault("environment", {})["REANA_JOB_HOSTPATH_MOUNTS"] = job_mount_config
if debug:
values_dict.setdefault("debug", {})["enabled"] = True

helm_install = "cat <<EOF | helm install reana helm/reana -n {namespace} --create-namespace --wait -f -\n{values}\nEOF".format(
namespace=namespace, values=yaml.dump(values_dict),
)
deploy_cmds = [
"helm dep update helm/reana",
helm_install,
"kubectl config set-context --current --namespace={}".format(namespace),
os.path.join(get_srcdir("reana"), "scripts/create-admin-user.sh"),
]:
]
dev_deploy_cmds = [
"reana-dev git-submodule --update",
"reana-dev python-install-eggs",
]
cmds = dev_deploy_cmds + deploy_cmds if debug else deploy_cmds
for cmd in cmds:
run_command(cmd, "reana")


Expand All @@ -1673,39 +1767,24 @@ def cluster_undeploy(): # noqa: D301


@click.option(
"-m",
"--mount",
is_flag=True,
default=False,
help="Should we mount /var/reana from host?"
)
@click.option(
"--recreate",
"-r",
"-d",
"--debug",
is_flag=True,
default=False,
help="Destroy and recreate cluster from scratch?",
help="Should we deploy REANA in debug mode? ",
)
@cli.command(name="run-ci")
def run_ci(mount, recreate): # noqa: D301
def run_ci(debug): # noqa: D301
"""Run CI build.

Builds and installs REANA and runs a demo example. Optionally, destroys
and recreates cluster from scratch."""
if recreate:
for cmd in [
"reana-dev cluster-delete",
mount and "reana-dev cluster-create -m" or "reana-dev cluster-create",
"reana-dev docker-pull -c reana -c DEMO",
"reana-dev kind-load-docker-image -c reana -c DEMO",
]:
run_command(cmd, "reana")
Builds and installs REANA and runs a demo example.
"""
for cmd in [
"reana-dev cluster-undeploy",
"reana-dev client-install",
"reana-dev cluster-build",
"reana-dev cluster-build" + (" --debug" if debug else ""),
"reana-dev kind-load-docker-image -c CLUSTER --exclude-components=r-ui,r-a-vomsproxy",
"reana-dev cluster-deploy",
"reana-dev cluster-deploy" + (" --debug" if debug else ""),
"eval $(reana-dev client-setup-environment) && reana-dev run-example",
]:
run_command(cmd, "reana")
Expand All @@ -1727,13 +1806,10 @@ def client_setup_environment(server_hostname, insecure_url): # noqa: D301
export_lines.append(
component_export_line.format(
env_var_name="REANA_SERVER_URL",
env_var_value=server_hostname or get_external_url(insecure_url),
env_var_value=server_hostname or "https://localhost:30443",
)
)
get_access_token_cmd = (
"kubectl get secret -o json "
f'{get_prefixed_component_name("admin-access-token")}'
)
get_access_token_cmd = "kubectl get secret -o json reana-admin-access-token"
secret_json = json.loads(
subprocess.check_output(get_access_token_cmd, shell=True).decode()
)
Expand Down Expand Up @@ -1905,39 +1981,76 @@ def run_example(
@click.option(
"-m",
"--mount",
"mounts",
multiple=True,
callback=volume_mounts_to_list,
help="Which local directories would be mounted in the Kind nodes? local_path:cluster_node_path",
)
@click.option(
"--debug",
is_flag=True,
default=False,
help="Should we mount /var/reana from host?"
help="Should we mount the REANA source code for live code updates?",
)
@click.option("--worker-nodes", default=0, help="How many worker nodes? default 0")
@cli.command(name="cluster-create")
def cluster_create(mount):
def cluster_create(mounts, debug, worker_nodes):
"""Create cluster."""
cmd = """cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 30080
hostPort: 30080
protocol: TCP
- containerPort: 30443
hostPort: 30443
protocol: TCP"""
if mount:
cmd += """
extraMounts:
- hostPath: /var/reana
containerPath: /var/reana"""
cmd += """
EOF"""
run_command(cmd, "reana")

class literal_str(str):
pass

def literal_unicode_str(dumper, data):
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")

def add_volume_mounts(node):
"""Add needed volumen mounts to the provided node."""

yaml.add_representer(literal_str, literal_unicode_str)

control_plane = {
"role": "control-plane",
"kubeadmConfigPatches": [
literal_str(
"kind: InitConfiguration\n"
"nodeRegistration:\n"
" kubeletExtraArgs:\n"
' node-labels: "ingress-ready=true"\n'
)
],
"extraPortMappings": [
{"containerPort": 30080, "hostPort": 30080, "protocol": "TCP"},
{"containerPort": 30443, "hostPort": 30443, "protocol": "TCP"},
],
}

if debug:
mounts.append({"hostPath": find_reana_srcdir(), "containerPath": "/code"})
control_plane["extraPortMappings"].extend(
[
{"containerPort": 31984, "hostPort": 31984, "protocol": "TCP"},
{"containerPort": 32580, "hostPort": 32580, "protocol": "TCP"},
]
)

nodes = [{"role": "worker"} for _ in range(worker_nodes)] + [control_plane]
for node in nodes:
node["extraMounts"] = mounts

cluster_config = {
"kind": "Cluster",
"apiVersion": "kind.x-k8s.io/v1alpha4",
"nodes": nodes,
}

cluster_create = "cat <<EOF | kind create cluster --config=-\n{cluster_config}\nEOF"
cluster_create = cluster_create.format(cluster_config=yaml.dump(cluster_config))
for cmd in [
cluster_create,
"reana-dev docker-pull -c reana -c DEMO",
"reana-dev kind-load-docker-image -c reana -c DEMO",
]:
run_command(cmd, "reana")


@cli.command(name="cluster-pause")
Expand Down
32 changes: 0 additions & 32 deletions reana/k8s_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,38 +48,6 @@ def exec_into_component(component_name, command):
return output.decode("UTF-8")


def get_external_url(insecure=False):
"""Get external IP and port to access REANA API.

:param insecure: Whether the URL should be insecure (http) or secure
(https).
:return: Returns a string which represents the full URL to access REANA.
"""
try:
minikube_ip = (
subprocess.check_output(["minikube", "ip", "--profile", INSTANCE_NAME])
.strip()
.decode("UTF-8")
)
except:
minikube_ip = "0.0.0.0"
# get service ports
traefik_name = get_prefixed_component_name("traefik")
server_name = get_prefixed_component_name("server")
external_ips, external_ports = get_service_ips_and_ports(traefik_name)
if not external_ports:
external_ips, external_ports = get_service_ips_and_ports(server_name)
if external_ports.get("https") and not insecure:
scheme = "https"
else:
scheme = "http"
return "{scheme}://{host}:{port}".format(
scheme=scheme,
host=minikube_ip or external_ips[0],
port=external_ports.get(scheme),
)


def get_service_ips_and_ports(component_name):
"""Get external IPs and ports for a given component service.

Expand Down
10 changes: 10 additions & 0 deletions scripts/create-admin-user.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ fi
# Get REANA Server pod name
REANA_SERVER=$(kubectl get pod -l "app=$instance_name-server" | grep Running | awk '{print $1}')

# Wait for DB to be ready
REANA_DB=$(kubectl get pod -l "app=$instance_name-db" | grep Running | awk '{print $1}')
echo $REANA_DB
while [ "0" -ne "$(kubectl exec "$REANA_DB" -- pg_isready -U reana -h 127.0.0.1 -p 5432 &> /dev/null && echo $? || echo 1)" ]
do
echo "Waiting for REANA-DB to be ready."
sleep 5;
done
echo "REANA-DB ready"

# Initialise DB
kubectl exec "$REANA_SERVER" -- ./scripts/setup

Expand Down