aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md81
-rw-r--r--moon_manager/tests/unit_python/api/test_data.py168
-rw-r--r--moon_manager/tests/unit_python/api/utilities.py16
-rw-r--r--moon_orchestrator/moon_orchestrator/api/pods.py55
-rw-r--r--moon_orchestrator/moon_orchestrator/drivers.py4
-rw-r--r--moon_orchestrator/tests/unit_python/test_pods.py48
-rw-r--r--tests/README.md77
-rw-r--r--tests/functional/README.md27
-rw-r--r--tests/functional/run_tests.sh3
-rw-r--r--tests/python_unit/README.md5
-rw-r--r--tests/python_unit/run_tests.sh12
-rw-r--r--tools/moon_jenkins/Dockerfile9
-rw-r--r--tools/moon_jenkins/Jenkinsfile24
-rw-r--r--tools/moon_jenkins/README.md37
-rw-r--r--tools/moon_jenkins/docker-compose.yml22
-rw-r--r--tools/moon_jenkins/images/Create Multibranch Pipeline.pngbin0 -> 55639 bytes
-rw-r--r--tools/moon_jenkins/images/Git Source Multibranch Pipeline.pngbin0 -> 31054 bytes
-rw-r--r--tools/moon_jenkins/images/Multibranch Pipeline Log.pngbin0 -> 55231 bytes
-rw-r--r--tools/moon_jenkins/images/Select Source Multibranch Pipeline.pngbin0 -> 23375 bytes
-rw-r--r--tools/moon_jenkins/plugins.txt100
-rw-r--r--tools/moon_jenkins/security.groovy20
-rw-r--r--tools/moon_jenkins/setenv.groovy34
-rw-r--r--tools/moon_kubernetes/README.md33
23 files changed, 605 insertions, 170 deletions
diff --git a/README.md b/README.md
index ecb868b3..22ae99e9 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,16 @@
# Moon
__Version 4.3__
-
This directory contains all the modules for running the Moon platform.
-## Platform Setup
+
+## Platform
+### Setup
- [Docker installation](tools/moon_kubernetes/README.md)
- [kubeadm installation](tools/moon_kubernetes/README.md)
- [Moon deployment](tools/moon_kubernetes/README.md)
- [OpenStack deployment](tools/openstack/README.md)
-
-## Micro-service Architecture
+### Micro-service Architecture
The Moon platform is composed on the following components/containers:
- *consul*: a Consul configuration server
- *db*: a MySQL database server
@@ -23,83 +23,30 @@ The Moon platform is composed on the following components/containers:
## Manipulation
### moon_gui
-The Moon platform comes with a graphical user interface which can be used with
-a web browser at this URL `http://$MOON_HOST:30002`
-
-You will be asked to put a login and password. Those elements are the login and password
-of the Keystone server, if you didn't modify the Keystone server, you will find the
-login and password here `http://$MOON_HOST:30005/ui/#/dc1/kv/openstack/keystone/edit`
+The web access of Moon is through the URL `http://$MOON_HOST:30002` with the login and password of Keystone.
+The default login and password can be found here: `http://$MOON_HOST:30005/ui/#/dc1/kv/openstack/keystone/edit`.
**WARNING: the password is in clear text, this is a known security issue.**
### moon_manager
-The Moon platform can also be requested through its API `http://$MOON_HOST:30001`
+The REST access of Moon is through `http://$MOON_HOST:30001`
-**WARNING: By default, no login/password will be needed because of
-the configuration which is in DEV mode.**
+**WARNING: By default, no login/password will be needed because of the configuration which is in DEV mode.**
-If you want more security, you have to update the configuration of the Keystone server here:
-`http://$MOON_HOST:30005/ui/#/dc1/kv/openstack/keystone/edit`
-by modifying the `check_token` argument to `yes`.
-If you write this modification, your requests to Moon API must always include a valid token
-taken from the Keystone server. This token must be place in the header of the request
-(`X-Auth-Token`).
+For more security, update `http://$MOON_HOST:30005/ui/#/dc1/kv/openstack/keystone/edit` by modifying the `check_token` argument to `yes`
+Requests to Moon API must include a valid token taken from Keystone in the header of `X-Auth-Token`.
-### End-to-end Functional Test
-Check if the Manager API is running:
+Check if the Manager API is running with:
```bash
curl http://$MOON_HOST:30001
curl http://$MOON_HOST:30001/pdp
curl http://$MOON_HOST:30001/policies
```
-### Consul Check
-Check the Consul service for
-- *Components/Manager*, e.g.
-```json
-{
- "port": 8082,
- "bind": "0.0.0.0",
- "hostname": "manager",
- "container": "wukongsun/moon_manager:v4.3.1",
- "external": {
- "port": 30001,
- "hostname": "$MOON_HOST"
- }
-}
-```
-- *OpenStack/Keystone*: e.g.
-```json
-{
- "url": "http://keystone:5000/v3",
- "user": "admin",
- "password": "p4ssw0rd",
- "domain": "default",
- "project": "admin",
- "check_token": false,
- "certificate": false,
- "external": {
- "url": "http://$MOON_HOST:30006/v3"
- }
-}
-```
-
-### Tests
-Launch functional [test scenario](tests/functional/scenario_enabled) :
-```bash
-sudo pip install python_moonclient --upgrade
-cd $MOON_HOME/tests/functional/scenario_tests
-moon_create_pdp --consul-host=$MOON_HOST --consul-port=30005 -v rbac_large.py
-moon_get_keystone_project --consul-host=$MOON_HOST --consul-port=30005
-moon_get_pdp --consul-host=$MOON_HOST --consul-port=30005
-moon_map_pdp_to_project "<pdp_id>" "<keystone_project_id>"
-moon_send_authz_to_wrapper --consul-host=$MOON_HOST --consul-port=30005 --authz-host=$WRAPPER_HOST --authz-port=$WRAPPER_PORT -v rbac_large.py
-```
-To retrieve the wrapper information, use the following command:
-```bash
-kubectl get -n moon services | grep wrapper
-```
+## Tests
+- [Python Unit Test](tests/python_unit/README.md)
+- [Functional Test](tests/functional/README.md)
## Annexe
diff --git a/moon_manager/tests/unit_python/api/test_data.py b/moon_manager/tests/unit_python/api/test_data.py
new file mode 100644
index 00000000..87a80c69
--- /dev/null
+++ b/moon_manager/tests/unit_python/api/test_data.py
@@ -0,0 +1,168 @@
+import api.utilities as utilities
+import json
+
+
+# subject_categories_test
+
+
+def get_subject_data(client, policy_id):
+ req = client.get("/policies/{}/subject_data".format(policy_id))
+ subject_data = utilities.get_json(req.data)
+ return req, subject_data
+
+
+def add_subject_data(client, name, policy_id, category_id):
+ data = {
+ "name": name,
+ "description": "description of {}".format(name)
+ }
+ req = client.post("/policies/{}/subject_data/{}".format(policy_id, category_id), data=json.dumps(data),
+ headers={'Content-Type': 'application/json'})
+ subject_data = utilities.get_json(req.data)
+ return req, subject_data
+
+
+def delete_subject_data(client, policy_id):
+ req = client.delete("/policies/{}/subject_data".format(policy_id))
+ return req
+
+
+def test_get_subject_data():
+ policy_id = utilities.get_policy_id()
+ client = utilities.register_client()
+ req, subject_data = get_subject_data(client, policy_id)
+ assert req.status_code == 200
+ assert isinstance(subject_data, dict)
+ assert "subject_data" in subject_data
+
+
+def test_add_subject_data():
+ policy_id = utilities.get_policy_id()
+ client = utilities.register_client()
+ req, subject_data = add_subject_data(client, "testuser", policy_id, "111")
+ assert req.status_code == 200
+ assert isinstance(subject_data, dict)
+ value = subject_data["subject_data"]['data']
+ assert "subject_data" in subject_data
+ id = list(value.keys())[0]
+ assert value[id]['name'] == "testuser"
+ assert value[id]['description'] == "description of {}".format("testuser")
+
+
+def test_delete_subject_data():
+ client = utilities.register_client()
+ policy_id = utilities.get_policy_id()
+ success_req = delete_subject_data(client, policy_id)
+ assert success_req.status_code == 200
+
+# ---------------------------------------------------------------------------
+
+# object_categories_test
+
+
+def get_object_data(client, policy_id):
+ req = client.get("/policies/{}/object_data".format(policy_id))
+ object_data = utilities.get_json(req.data)
+ return req, object_data
+
+
+def add_object_data(client, name, policy_id, category_id):
+ data = {
+ "name": name,
+ "description": "description of {}".format(name)
+ }
+ req = client.post("/policies/{}/object_data/{}".format(policy_id, category_id), data=json.dumps(data),
+ headers={'Content-Type': 'application/json'})
+ object_data = utilities.get_json(req.data)
+ return req, object_data
+
+
+def delete_object_data(client, policy_id):
+ req = client.delete("/policies/{}/object_data".format(policy_id))
+ return req
+
+
+def test_get_object_data():
+ policy_id = utilities.get_policy_id()
+ client = utilities.register_client()
+ req, object_data = get_object_data(client, policy_id)
+ assert req.status_code == 200
+ assert isinstance(object_data, dict)
+ assert "object_data" in object_data
+
+
+def test_add_object_data():
+ policy_id = utilities.get_policy_id()
+ client = utilities.register_client()
+ req, object_data = add_object_data(client, "testuser", policy_id, "111")
+ assert req.status_code == 200
+ assert isinstance(object_data, dict)
+ value = object_data["object_data"]['data']
+ assert "object_data" in object_data
+ id = list(value.keys())[0]
+ assert value[id]['value']['name'] == "testuser"
+ assert value[id]['value']['description'] == "description of {}".format("testuser")
+
+
+def test_delete_object_data():
+ client = utilities.register_client()
+ policy_id = utilities.get_policy_id()
+ success_req = delete_object_data(client, policy_id)
+ assert success_req.status_code == 200
+
+# ---------------------------------------------------------------------------
+
+# action_categories_test
+
+
+def get_action_data(client, policy_id):
+ req = client.get("/policies/{}/action_data".format(policy_id))
+ action_data = utilities.get_json(req.data)
+ return req, action_data
+
+
+def add_action_data(client, name, policy_id, category_id):
+ data = {
+ "name": name,
+ "description": "description of {}".format(name)
+ }
+ req = client.post("/policies/{}/action_data/{}".format(policy_id, category_id), data=json.dumps(data),
+ headers={'Content-Type': 'application/json'})
+ action_data = utilities.get_json(req.data)
+ return req, action_data
+
+
+def delete_action_data(client, policy_id):
+ req = client.delete("/policies/{}/action_data".format(policy_id))
+ return req
+
+
+def test_get_action_data():
+ policy_id = utilities.get_policy_id()
+ client = utilities.register_client()
+ req, action_data = get_action_data(client, policy_id)
+ assert req.status_code == 200
+ assert isinstance(action_data, dict)
+ assert "action_data" in action_data
+
+
+def test_add_action_data():
+ policy_id = utilities.get_policy_id()
+ client = utilities.register_client()
+ req, action_data = add_action_data(client, "testuser", policy_id, "111")
+ assert req.status_code == 200
+ assert isinstance(action_data, dict)
+ value = action_data["action_data"]['data']
+ assert "action_data" in action_data
+ id = list(value.keys())[0]
+ assert value[id]['value']['name'] == "testuser"
+ assert value[id]['value']['description'] == "description of {}".format("testuser")
+
+
+def test_delete_action_data():
+ client = utilities.register_client()
+ policy_id = utilities.get_policy_id()
+ success_req = delete_action_data(client, policy_id)
+ assert success_req.status_code == 200
+
+# --------------------------------------------------------------------------- \ No newline at end of file
diff --git a/moon_manager/tests/unit_python/api/utilities.py b/moon_manager/tests/unit_python/api/utilities.py
index a2b0689b..66ca30c5 100644
--- a/moon_manager/tests/unit_python/api/utilities.py
+++ b/moon_manager/tests/unit_python/api/utilities.py
@@ -9,4 +9,18 @@ def register_client():
import moon_manager.server
server = moon_manager.server.create_server()
client = server.app.test_client()
- return client \ No newline at end of file
+ return client
+
+
+def get_policy_id():
+ import api.test_policies as policies
+ client = register_client()
+ policy_id = ''
+ req, policy = policies.get_policies(client)
+ for id in policy['policies']:
+ if id:
+ policy_id = id
+ break
+ if not policy_id:
+ policies.add_policies(client, "testuser")
+ return policy_id
diff --git a/moon_orchestrator/moon_orchestrator/api/pods.py b/moon_orchestrator/moon_orchestrator/api/pods.py
index a83ca9ae..31ae31de 100644
--- a/moon_orchestrator/moon_orchestrator/api/pods.py
+++ b/moon_orchestrator/moon_orchestrator/api/pods.py
@@ -44,15 +44,18 @@ class Pods(Resource):
:internal_api: get_pdp
"""
pods = {}
- if uuid:
- return {"pods": self.driver.get_pods(uuid)}
- for _pod_key, _pod_values in self.driver.get_pods().items():
- pods[_pod_key] = []
- for _pod_value in _pod_values:
- if _pod_value['namespace'] != "moon":
- continue
- pods[_pod_key].append(_pod_value)
- return {"pods": pods}
+ try:
+ if uuid:
+ return {"pods": self.driver.get_pods(uuid)}
+ for _pod_key, _pod_values in self.driver.get_pods().items():
+ pods[_pod_key] = []
+ for _pod_value in _pod_values:
+ if _pod_value['namespace'] != "moon":
+ continue
+ pods[_pod_key].append(_pod_value)
+ return {"pods": pods}
+ except Exception as e:
+ return {"result": False, "message": str(e)}, 500
@check_auth
def post(self, uuid=None, user_id=None):
@@ -74,21 +77,24 @@ class Pods(Resource):
}
"""
logger.debug("POST param={}".format(request.json))
- self.driver.create_pipeline(
- request.json.get("keystone_project_id"),
- request.json.get("pdp_id"),
- request.json.get("security_pipeline"),
- manager_data=request.json,
- active_context=None,
- active_context_name=None)
- pods = {}
- for _pod_key, _pod_values in self.driver.get_pods().items():
- pods[_pod_key] = []
- for _pod_value in _pod_values:
- if _pod_value['namespace'] != "moon":
- continue
- pods[_pod_key].append(_pod_value)
- return {"pods": pods}
+ try:
+ self.driver.create_pipeline(
+ request.json.get("keystone_project_id"),
+ request.json.get("pdp_id"),
+ request.json.get("security_pipeline"),
+ manager_data=request.json,
+ active_context=None,
+ active_context_name=None)
+ pods = {}
+ for _pod_key, _pod_values in self.driver.get_pods().items():
+ pods[_pod_key] = []
+ for _pod_value in _pod_values:
+ if _pod_value['namespace'] != "moon":
+ continue
+ pods[_pod_key].append(_pod_value)
+ return {"pods": pods}
+ except Exception as e:
+ return {"result": False, "message": str(e)}, 500
@check_auth
def delete(self, uuid=None, user_id=None):
@@ -128,4 +134,3 @@ class Pods(Resource):
:internal_api: update_pdp
"""
return {"pods": None}
-
diff --git a/moon_orchestrator/moon_orchestrator/drivers.py b/moon_orchestrator/moon_orchestrator/drivers.py
index b21f2639..9c230cce 100644
--- a/moon_orchestrator/moon_orchestrator/drivers.py
+++ b/moon_orchestrator/moon_orchestrator/drivers.py
@@ -259,6 +259,8 @@ class K8S(Driver):
},
]
logger.debug("data={}".format(data))
+ # When policies and models are empty, is it right that it returns 200 ?
+ # Should it return no found policies or models ?
policies = manager_data.get('policies')
if not policies:
logger.info("No policy data from Manager, trying to get them")
@@ -274,6 +276,8 @@ class K8S(Driver):
"models", dict())
logger.debug("models={}".format(models))
+ if not policy_ids:
+ raise exceptions.PolicyUnknown
for policy_id in policy_ids:
if policy_id in policies:
genre = policies[policy_id].get("genre", "authz")
diff --git a/moon_orchestrator/tests/unit_python/test_pods.py b/moon_orchestrator/tests/unit_python/test_pods.py
index 0a5a5ba5..f760aa62 100644
--- a/moon_orchestrator/tests/unit_python/test_pods.py
+++ b/moon_orchestrator/tests/unit_python/test_pods.py
@@ -17,6 +17,20 @@ def test_get_pods(context, monkeypatch):
assert "pods" in data
+def test_get_pods_failure(context, monkeypatch):
+ patch_k8s(monkeypatch)
+
+ import moon_orchestrator.server
+ server = moon_orchestrator.server.create_server()
+ _client = server.app.test_client()
+ req = _client.get("/pods/invalid")
+ assert req.status_code == 200
+ assert req.data
+ data = get_json(req.data)
+ assert isinstance(data, dict)
+ assert not data["pods"]
+
+
def test_add_pods(context, monkeypatch):
patch_k8s(monkeypatch)
@@ -38,6 +52,40 @@ def test_add_pods(context, monkeypatch):
assert data["pods"]
+def test_add_pods_with_no_data(context, monkeypatch):
+ patch_k8s(monkeypatch)
+ import moon_orchestrator.server
+ server = moon_orchestrator.server.create_server()
+ _client = server.app.test_client()
+ req = _client.post("/pods", data=json.dumps({}),
+ headers={'Content-Type': 'application/json'})
+ assert req.status_code == 500
+ assert req.data
+ data = get_json(req.data)
+ assert '400: Policy Unknown' in data['message']
+
+
+def test_add_pods_with_no_policies_no_models(context, monkeypatch, no_requests):
+ patch_k8s(monkeypatch)
+
+ import moon_orchestrator.server
+ server = moon_orchestrator.server.create_server()
+ _client = server.app.test_client()
+ no_requests.get("http://manager:8082/policies",
+ json={'policies': {}})
+
+ no_requests.get("http://manager:8082/models",
+ json={'models': {}})
+ data = {
+ "keystone_project_id": context.get('project_id'),
+ "pdp_id": context.get('pdp_id'),
+ "security_pipeline": context.get('security_pipeline'),
+ }
+ req = _client.post("/pods", data=json.dumps(data),
+ headers={'Content-Type': 'application/json'})
+ assert req.status_code == 200
+
+
def test_delete_pods(context, monkeypatch):
# TODO
pass
diff --git a/tests/README.md b/tests/README.md
deleted file mode 100644
index c1437a2d..00000000
--- a/tests/README.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# Moon Tests
-## Functional Tests
-### Test Platform Setup
-#### Docker Installation
-```bash
-apt update
-apt install -y docker.io
-```
-
-#### Kubeadm Installation
-see: https://kubernetes.io/docs/setup/independent/install-kubeadm/
-```bash
-apt-get update && apt-get install -y apt-transport-https
-curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
-cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
-deb http://apt.kubernetes.io/ kubernetes-xenial main
-EOF
-apt-get update
-apt-get install -y kubelet kubeadm kubectl
-```
-
-#### K8S Initialisation
-```bash
-cd $MOON_HOME
-bash tools/moon_kubernetes/init_k8s.sh
-```
-
-Wait until all the kubeadm containers are in the `running` state:
-```bash
-watch kubectl get po --namespace=kube-system
-```
-
-You must see something like this:
-
- $ kubectl get po --namespace=kube-system
- NAME READY STATUS RESTARTS AGE
- calico-etcd-7qgjb 1/1 Running 0 1h
- calico-node-f8zvm 2/2 Running 1 1h
- calico-policy-controller-59fc4f7888-ns9kv 1/1 Running 0 1h
- etcd-varuna 1/1 Running 0 1h
- kube-apiserver-varuna 1/1 Running 0 1h
- kube-controller-manager-varuna 1/1 Running 0 1h
- kube-dns-bfbb49cd7-rgqxn 3/3 Running 0 1h
- kube-proxy-x88wg 1/1 Running 0 1h
- kube-scheduler-varuna 1/1 Running 0 1h
-
-
-#### Deploy Moon
-```bash
-cd $MOON_HOME
-sudo bash tools/moon_kubernetes/start_moon.sh
-```
-
-Wait until all the Moon containers are in the `running` state:
-```bash
-watch kubectl get po --namespace=moon
-```
-
-You must see something like this:
-
- $ kubectl get po --namespace=moon
- NAME READY STATUS RESTARTS AGE
- consul-57b6d66975-9qnfx 1/1 Running 0 52m
- db-867f9c6666-bq8cf 1/1 Running 0 52m
- gui-bc9878b58-q288x 1/1 Running 0 51m
- keystone-7d9cdbb69f-bl6ln 1/1 Running 0 52m
- manager-5bfbb96988-2nvhd 1/1 Running 0 51m
- manager-5bfbb96988-fg8vj 1/1 Running 0 51m
- manager-5bfbb96988-w9wnk 1/1 Running 0 51m
- orchestrator-65d8fb4574-tnfx2 1/1 Running 0 51m
- wrapper-astonishing-748b7dcc4f-ngsvp 1/1 Running 0 51m
-
-### Launch Functional for Target Module
-```bash
-cd $MOON_HOME
-sudo bash $TARGET_MODULE/tests/functional_pod/run_functional_tests.sh
-```
diff --git a/tests/functional/README.md b/tests/functional/README.md
new file mode 100644
index 00000000..4cac22b6
--- /dev/null
+++ b/tests/functional/README.md
@@ -0,0 +1,27 @@
+# Moon Functional Test
+
+[Test Platform Setup](../../tools/moon_kubernetes/README.md)
+
+
+### Pod Functional Test
+Launch functional [test scenario](tests/functional/scenario_enabled) :
+```bash
+sudo pip install python_moonclient --upgrade
+cd $MOON_HOME/tests/functional/scenario_tests
+moon_create_pdp --consul-host=$MOON_HOST --consul-port=30005 -v rbac_large.py
+moon_get_keystone_project --consul-host=$MOON_HOST --consul-port=30005
+moon_get_pdp --consul-host=$MOON_HOST --consul-port=30005
+moon_map_pdp_to_project "<pdp_id>" "<keystone_project_id>"
+moon_send_authz_to_wrapper --consul-host=$MOON_HOST --consul-port=30005 --authz-host=$WRAPPER_HOST --authz-port=$WRAPPER_PORT -v rbac_large.py
+```
+
+To retrieve the wrapper information, use the following command:
+```bash
+kubectl get -n moon services | grep wrapper
+```
+
+Launch functional tests:
+```bash
+cd $MOON_HOME
+sudo bash $TARGET_MODULE/tests/functional_pod/run_functional_tests.sh
+```
diff --git a/tests/functional/run_tests.sh b/tests/functional/run_tests.sh
new file mode 100644
index 00000000..ced0f9f7
--- /dev/null
+++ b/tests/functional/run_tests.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+echo "starting Moon Functional Tests"
diff --git a/tests/python_unit/README.md b/tests/python_unit/README.md
new file mode 100644
index 00000000..a399f834
--- /dev/null
+++ b/tests/python_unit/README.md
@@ -0,0 +1,5 @@
+# Python Unit Test
+
+```bash
+bash run_tests.sh
+```
diff --git a/tests/python_unit/run_tests.sh b/tests/python_unit/run_tests.sh
new file mode 100644
index 00000000..33c1ab98
--- /dev/null
+++ b/tests/python_unit/run_tests.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+echo "starting Moon Functional Tests"
+
+cd python_moonutilities
+docker run --rm --volume $(pwd):/data wukongsun/moon_python_unit_test:latest
+
+cd ../python_moondb
+docker run --rm --volume $(pwd):/data wukongsun/moon_python_unit_test:latest
+
+cd ../python_moonclient
+docker run --rm --volume $(pwd):/data wukongsun/moon_python_unit_test:latest \ No newline at end of file
diff --git a/tools/moon_jenkins/Dockerfile b/tools/moon_jenkins/Dockerfile
new file mode 100644
index 00000000..573d4ac8
--- /dev/null
+++ b/tools/moon_jenkins/Dockerfile
@@ -0,0 +1,9 @@
+FROM jenkinsci/blueocean
+
+ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
+
+COPY security.groovy /usr/share/jenkins/ref/init.groovy.d/security.groovy
+COPY setenv.groovy /usr/share/jenkins/ref/init.groovy.d/setenv.groovy
+
+COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
+RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt \ No newline at end of file
diff --git a/tools/moon_jenkins/Jenkinsfile b/tools/moon_jenkins/Jenkinsfile
new file mode 100644
index 00000000..7e0e07c0
--- /dev/null
+++ b/tools/moon_jenkins/Jenkinsfile
@@ -0,0 +1,24 @@
+pipeline {
+ agent {
+ docker {
+ image 'wukongsun/moon_python_unit_test'
+ args '-e moon_home=${moon_home}'
+ }
+ }
+ stages {
+ stage('Python Unit Test') {
+ steps {
+ script {
+ sh("cd ${moon_home}/tests/python_unit")
+ sh("bash run_tests")
+ }
+ }
+ }
+ stage('Functional Test') {
+ script {
+ sh("cd ${moon_home}/tests/functional")
+ sh("bash run_tests")
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/moon_jenkins/README.md b/tools/moon_jenkins/README.md
new file mode 100644
index 00000000..684b351c
--- /dev/null
+++ b/tools/moon_jenkins/README.md
@@ -0,0 +1,37 @@
+# Moon Jenkins
+The aim of this repo is to give a quick way to start with jenkins in containers.
+These were the aims of the automation:
+- minimal interaction with Jenkins GUI - the plugins in plugins.txt are installed automatically, the admin user is setup based on environment variables, proxy variables are inherited from environment
+- the build of the custom image is integrated in the same workflow
+
+## Prerequisites
+- one host running a newer version of the docker-engine
+- docker-compose 1.18.0
+
+## Usage
+- Setup secrets:
+```bash
+export JENKINS_USER=admin
+export JENKINS_PASSWORD=admin
+```
+- Deploy jenkins:
+```bash
+docker-compose up -d
+ ```
+- Test: Jenkins GUI can be available on `http://<docker host IP>:8080`
+
+
+## Pipeline Creation
+You may find bellow an example of pipeline creation using BlueOcean interface.
+As example I used a clone (https://github.com/brutus333/moon.git) of the moon project (https://git.opnfv.org/moon/)
+
+Click on "Create a new job" in the classical Jenkins UI and follow the steps highlighted bellow:
+
+![Create Multibranch Pipeline](images/Create%20Multibranch%20Pipeline.png)
+![Select Source](images/Select%20Source%20Multibranch%20Pipeline.png)
+![Configure Source](images/Git%20Source%20Multibranch%20Pipeline.png)
+![Multibranch Pipeline Log](images/Multibranch%20Pipeline%20Log.png)
+
+Clicking on BlueOcean shows the pipeline in the blueocean interface:
+
+![Blue Ocean Pipeline success](images/blue%20ocean%20success%20pipeline.png)
diff --git a/tools/moon_jenkins/docker-compose.yml b/tools/moon_jenkins/docker-compose.yml
new file mode 100644
index 00000000..2329f7bd
--- /dev/null
+++ b/tools/moon_jenkins/docker-compose.yml
@@ -0,0 +1,22 @@
+version: '3.1'
+
+services:
+
+ jenkins:
+ build:
+ context: .
+ args:
+ image: blueocean:v0.3
+ ports:
+ - 8080:8080
+ - 50000:50000
+ environment:
+ - jenkins_user=${JENKINS_USER}
+ - jenkins_password=${JENKINS_PASSWORD}
+ volumes:
+ - jenkins-data:/var/jenkins_home
+ - /var/run/docker.sock:/var/run/docker.sock
+ user: root
+
+volumes:
+ jenkins-data:
diff --git a/tools/moon_jenkins/images/Create Multibranch Pipeline.png b/tools/moon_jenkins/images/Create Multibranch Pipeline.png
new file mode 100644
index 00000000..c71415c0
--- /dev/null
+++ b/tools/moon_jenkins/images/Create Multibranch Pipeline.png
Binary files differ
diff --git a/tools/moon_jenkins/images/Git Source Multibranch Pipeline.png b/tools/moon_jenkins/images/Git Source Multibranch Pipeline.png
new file mode 100644
index 00000000..dd37f217
--- /dev/null
+++ b/tools/moon_jenkins/images/Git Source Multibranch Pipeline.png
Binary files differ
diff --git a/tools/moon_jenkins/images/Multibranch Pipeline Log.png b/tools/moon_jenkins/images/Multibranch Pipeline Log.png
new file mode 100644
index 00000000..a1905934
--- /dev/null
+++ b/tools/moon_jenkins/images/Multibranch Pipeline Log.png
Binary files differ
diff --git a/tools/moon_jenkins/images/Select Source Multibranch Pipeline.png b/tools/moon_jenkins/images/Select Source Multibranch Pipeline.png
new file mode 100644
index 00000000..eadbe916
--- /dev/null
+++ b/tools/moon_jenkins/images/Select Source Multibranch Pipeline.png
Binary files differ
diff --git a/tools/moon_jenkins/plugins.txt b/tools/moon_jenkins/plugins.txt
new file mode 100644
index 00000000..2463d029
--- /dev/null
+++ b/tools/moon_jenkins/plugins.txt
@@ -0,0 +1,100 @@
+ssh-credentials
+git
+blueocean-dashboard
+pipeline-model-api
+pipeline-graph-analysis
+workflow-support
+display-url-api
+blueocean-config
+workflow-cps
+branch-api
+blueocean-i18n
+workflow-job
+blueocean-bitbucket-pipeline
+favorite
+docker-commons
+pipeline-input-step
+blueocean-pipeline-api-impl
+workflow-api
+jackson2-api
+git-client
+blueocean-pipeline-scm-api
+blueocean
+pipeline-build-step
+jquery-detached
+matrix-project
+antisamy-markup-formatter
+pipeline-model-extensions
+docker-workflow
+github
+git-server
+authentication-tokens
+workflow-cps-global-lib
+pipeline-model-definition
+workflow-scm-step
+pipeline-model-declarative-agent
+cloudbees-bitbucket-branch-source
+script-security
+scm-api
+blueocean-rest
+variant
+sse-gateway
+htmlpublisher
+matrix-auth
+pubsub-light
+blueocean-github-pipeline
+token-macro
+credentials
+mercurial
+plain-credentials
+blueocean-events
+github-api
+blueocean-git-pipeline
+structs
+durable-task
+pipeline-milestone-step
+blueocean-pipeline-editor
+blueocean-web
+pipeline-stage-tags-metadata
+ace-editor
+blueocean-commons
+blueocean-jira
+blueocean-rest-impl
+workflow-step-api
+blueocean-personalization
+workflow-basic-steps
+blueocean-display-url
+jira
+pipeline-stage-step
+jsch
+blueocean-jwt
+cloudbees-folder
+credentials-binding
+github-branch-source
+apache-httpcomponents-client-4-api
+blueocean-autofavorite
+workflow-multibranch
+mailer
+workflow-durable-task-step
+junit
+command-launcher
+bouncycastle-api
+build-timeout
+timestamper
+resource-disposer
+ws-cleanup
+ant
+gradle
+pipeline-rest-api
+handlebars
+momentjs
+pipeline-stage-view
+workflow-aggregator
+pipeline-github-lib
+mapdb-api
+subversion
+ssh-slaves
+pam-auth
+ldap
+email-ext
+locale
diff --git a/tools/moon_jenkins/security.groovy b/tools/moon_jenkins/security.groovy
new file mode 100644
index 00000000..0fb5ff6e
--- /dev/null
+++ b/tools/moon_jenkins/security.groovy
@@ -0,0 +1,20 @@
+#!groovy
+
+import jenkins.model.*
+import hudson.security.*
+
+def instance = Jenkins.getInstance()
+
+def user = System.getenv()['jenkins_user']
+def pass = System.getenv()['jenkins_password']
+// Create user account
+def hudsonRealm = new HudsonPrivateSecurityRealm(false)
+hudsonRealm.createAccount(user,pass)
+instance.setSecurityRealm(hudsonRealm)
+
+// Enable matrix auth strategy and set my_user as admin
+def strategy = new GlobalMatrixAuthorizationStrategy()
+strategy.add(Jenkins.ADMINISTER, user)
+instance.setAuthorizationStrategy(strategy)
+
+instance.save()
diff --git a/tools/moon_jenkins/setenv.groovy b/tools/moon_jenkins/setenv.groovy
new file mode 100644
index 00000000..ab2dc137
--- /dev/null
+++ b/tools/moon_jenkins/setenv.groovy
@@ -0,0 +1,34 @@
+#!groovy
+
+import jenkins.*
+import jenkins.model.*
+import hudson.*
+import hudson.model.*
+
+instance = Jenkins.getInstance()
+globalNodeProperties = instance.getGlobalNodeProperties()
+
+envVarsNodePropertyList = globalNodeProperties.getAll(hudson.slaves.EnvironmentVariablesNodeProperty.class)
+
+newEnvVarsNodeProperty = null
+envVars = null
+
+if (envVarsNodePropertyList == null || envVarsNodePropertyList.size() == 0) {
+ newEnvVarsNodeProperty = new hudson.slaves.EnvironmentVariablesNodeProperty();
+ globalNodeProperties.add(newEnvVarsNodeProperty)
+ envVars = newEnvVarsNodeProperty.getEnvVars()
+} else {
+ envVars = envVarsNodePropertyList.get(0).getEnvVars()
+}
+
+http_proxy = System.getenv()['http_proxy']
+https_proxy = System.getenv()['https_proxy']
+
+if (http_proxy) {
+ envVars.put("http_proxy", System.getenv()['http_proxy'])
+}
+if (https_proxy) {
+ envVars.put("https_proxy", System.getenv()['https_proxy'])
+}
+
+instance.save()
diff --git a/tools/moon_kubernetes/README.md b/tools/moon_kubernetes/README.md
index 73d342fa..2077e580 100644
--- a/tools/moon_kubernetes/README.md
+++ b/tools/moon_kubernetes/README.md
@@ -78,6 +78,39 @@ You must see something like this:
manager-5bfbb96988-w9wnk 1/1 Running 0 51m
orchestrator-65d8fb4574-tnfx2 1/1 Running 0 51m
wrapper-astonishing-748b7dcc4f-ngsvp 1/1 Running 0 51m
+
+
+### Troubleshoot
+check *Consul* for:
+- *Components/Manager*, e.g.
+```json
+{
+ "port": 8082,
+ "bind": "0.0.0.0",
+ "hostname": "manager",
+ "container": "wukongsun/moon_manager:v4.3.1",
+ "external": {
+ "port": 30001,
+ "hostname": "$MOON_HOST"
+ }
+}
+```
+- *OpenStack/Keystone*: e.g.
+```json
+{
+ "url": "http://keystone:5000/v3",
+ "user": "admin",
+ "password": "p4ssw0rd",
+ "domain": "default",
+ "project": "admin",
+ "check_token": false,
+ "certificate": false,
+ "external": {
+ "url": "http://$MOON_HOST:30006/v3"
+ }
+}
+```
+
### Docker-K8S Port Mapping
```yamlex