Implement probe prototype

Change-Id: If30bd80701129d9fa892df0b1d37214a7b6c7a33
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..d621e7f
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+.tox
+venv-service
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7e0d89e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+.noseids
+.vscode
+build
+dist
+.coverage
+coverage.xml
+cover
+.tox
+.DS_Store
+nose2-results.xml
+venv-service
+*.pyc
\ No newline at end of file
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..df6881c
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,5 @@
+[gerrit]
+host=gerrit.opencord.org
+port=29418
+project=cord-workflow-probe.git
+defaultremote=origin
diff --git a/Dockerfile.synchronizer b/Dockerfile.synchronizer
new file mode 100644
index 0000000..f5c2e87
--- /dev/null
+++ b/Dockerfile.synchronizer
@@ -0,0 +1,61 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# docker build -t xosproject/cord-workflow-probe-synchronizer:candidate -f Dockerfile.synchronizer .
+
+# xosproject/cord-workflow-probe-synchronizer
+
+FROM xosproject/alpine-grpc-base:0.9.0
+
+# Install pip packages
+COPY requirements.txt /tmp/requirements.txt
+RUN pip install -r /tmp/requirements.txt \
+ && pip freeze > /var/xos/pip_freeze_cord_workflow_probe_service_`date -u +%Y%m%dT%H%M%S`
+
+# Copy files
+COPY xos/synchronizer /opt/xos/synchronizers/cord-workflow-probe
+COPY VERSION /opt/xos/synchronizers/cord-workflow-probe/
+
+WORKDIR "/opt/xos/synchronizers/cord-workflow-probe"
+
+# Label image
+ARG org_label_schema_schema_version=1.0
+ARG org_label_schema_name=cord-workflow-probe-synchronizer
+ARG org_label_schema_version=unknown
+ARG org_label_schema_vcs_url=unknown
+ARG org_label_schema_vcs_ref=unknown
+ARG org_label_schema_build_date=unknown
+ARG org_opencord_vcs_commit_date=unknown
+ARG org_opencord_component_chameleon_version=unknown
+ARG org_opencord_component_chameleon_vcs_url=unknown
+ARG org_opencord_component_chameleon_vcs_ref=unknown
+ARG org_opencord_component_xos_version=unknown
+ARG org_opencord_component_xos_vcs_url=unknown
+ARG org_opencord_component_xos_vcs_ref=unknown
+
+LABEL org.label-schema.schema-version=$org_label_schema_schema_version \
+      org.label-schema.name=$org_label_schema_name \
+      org.label-schema.version=$org_label_schema_version \
+      org.label-schema.vcs-url=$org_label_schema_vcs_url \
+      org.label-schema.vcs-ref=$org_label_schema_vcs_ref \
+      org.label-schema.build-date=$org_label_schema_build_date \
+      org.opencord.vcs-commit-date=$org_opencord_vcs_commit_date \
+      org.opencord.component.chameleon.version=$org_opencord_component_chameleon_version \
+      org.opencord.component.chameleon.vcs-url=$org_opencord_component_chameleon_vcs_url \
+      org.opencord.component.chameleon.vcs-ref=$org_opencord_component_chameleon_vcs_ref \
+      org.opencord.component.xos.version=$org_opencord_component_xos_version \
+      org.opencord.component.xos.vcs-url=$org_opencord_component_xos_vcs_url \
+      org.opencord.component.xos.vcs-ref=$org_opencord_component_xos_vcs_ref
+
+CMD ["/usr/bin/python", "/opt/xos/synchronizers/cord-workflow-probe/cord-workflow-probe-synchronizer.py"]
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..157939a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,68 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Configure shell
+SHELL = bash -e -o pipefail
+
+# Variables
+VERSION                  ?= $(shell cp -f ../VERSION . && cat ./VERSION)
+SERVICE_NAME             ?= $(notdir $(abspath .))
+SYNCHRONIZER_NAME        ?= cord-workflow-probe-synchronizer
+
+## Docker related
+DOCKER_REGISTRY          ?=
+DOCKER_REPOSITORY        ?=
+DOCKER_BUILD_ARGS        ?=
+DOCKER_TAG               ?= ${VERSION}
+DOCKER_IMAGENAME         := ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}${SYNCHRONIZER_NAME}:${DOCKER_TAG}
+
+## Docker labels. Only set ref and commit date if committed
+DOCKER_LABEL_VCS_URL     ?= $(shell git remote get-url $(shell git remote))
+DOCKER_LABEL_VCS_REF     ?= $(shell git diff-index --quiet HEAD -- && git rev-parse HEAD || echo "unknown")
+DOCKER_LABEL_COMMIT_DATE ?= $(shell git diff-index --quiet HEAD -- && git show -s --format=%cd --date=iso-strict HEAD || echo "unknown" )
+DOCKER_LABEL_BUILD_DATE  ?= $(shell date -u "+%Y-%m-%dT%H:%M:%SZ")
+
+all: test
+
+docker-build:
+	docker build $(DOCKER_BUILD_ARGS) \
+    -t ${DOCKER_IMAGENAME} \
+    --build-arg org_label_schema_version="${VERSION}" \
+    --build-arg org_label_schema_vcs_url="${DOCKER_LABEL_VCS_URL}" \
+    --build-arg org_label_schema_vcs_ref="${DOCKER_LABEL_VCS_REF}" \
+    --build-arg org_label_schema_build_date="${DOCKER_LABEL_BUILD_DATE}" \
+    --build-arg org_opencord_vcs_commit_date="${DOCKER_LABEL_COMMIT_DATE}" \
+    -f Dockerfile.synchronizer .
+
+docker-push:
+	docker push ${DOCKER_IMAGENAME}
+
+test: test-unit
+
+test-unit:
+	tox
+
+venv-service:
+	virtualenv $@;\
+    source ./$@/bin/activate ; set -u ;\
+    pip install -r requirements.txt
+
+clean:
+	find . -name '*.pyc' | xargs rm -f
+	rm -rf \
+    .tox \
+    venv-service \
+    xos/.coverage \
+    xos/coverage.xml \
+    xos/nose2-results.xml
\ No newline at end of file
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..40840e9
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,8 @@
+CORD Workflow Probe
+===================
+
+CORD Workflow Probe detects XOS Events (both bottom-up and top-down events) and pass them to
+CORD Workflow Controller for routing.
+
+CORD Workflow is implemented in XOS Synchronizer.
+
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..6c6aa7c
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.0
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..843481f
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+xossynchronizer~=3.2.6
+xosapi~=3.2.6
+xoskafka~=3.2.6
+cord-workflow-controller-client~=0.5.0
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..bb5d074
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,48 @@
+; Copyright 2019-present Open Networking Foundation
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+
+[tox]
+envlist = py27,py35,py36,py37
+skip_missing_interpreters = true
+skipsdist = True
+
+[testenv]
+deps =
+  -r requirements.txt
+  requests_mock
+  nose2
+  flake8
+
+changedir = xos
+commands =
+  nose2 -c ../tox.ini --verbose --junit-xml
+  flake8
+
+[flake8]
+max-line-length = 119
+exclude =
+  .tox
+
+[unittest]
+plugins = nose2.plugins.junitxml
+
+[junit-xml]
+path = nose2-results.xml
+
+[coverage]
+always-on = True
+omit = .tox
+coverage-report =
+  term
+  xml
\ No newline at end of file
diff --git a/xos/synchronizer/__init__.py b/xos/synchronizer/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/xos/synchronizer/config.yaml b/xos/synchronizer/config.yaml
new file mode 100644
index 0000000..4fd1440
--- /dev/null
+++ b/xos/synchronizer/config.yaml
@@ -0,0 +1,40 @@
+
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+name: cord-workflow-probe
+core_version: ">=2.2.1"
+required_models:
+  - RCORDSubscriber
+  - ONUDevice
+model_policies_dir: "/opt/xos/synchronizers/cord-workflow-probe/model_policies"
+models_dir: "/opt/xos/synchronizers/cord-workflow-probe/models"
+event_steps_dir: "/opt/xos/synchronizers/cord-workflow-probe/event_steps"
+logging:
+  version: 1
+  handlers:
+    console:
+      class: logging.StreamHandler
+    file:
+      class: logging.handlers.RotatingFileHandler
+      filename: /var/log/xos.log
+      maxBytes: 10485760
+      backupCount: 5
+  loggers:
+    'multistructlog':
+      handlers:
+          - console
+          - file
+      level: DEBUG
\ No newline at end of file
diff --git a/xos/synchronizer/cord-workflow-probe-synchronizer.py b/xos/synchronizer/cord-workflow-probe-synchronizer.py
new file mode 100644
index 0000000..0b90e04
--- /dev/null
+++ b/xos/synchronizer/cord-workflow-probe-synchronizer.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This imports and runs ../../xos-observer.py
+
+import os
+from xossynchronizer import Synchronizer
+from xosconfig import Config
+
+
+base_config_file = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/config.yaml')
+mounted_config_file = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/mounted_config.yaml')
+
+if os.path.isfile(mounted_config_file):
+    Config.init(base_config_file, 'synchronizer-config-schema.yaml', mounted_config_file)
+else:
+    Config.init(base_config_file, 'synchronizer-config-schema.yaml')
+
+Synchronizer().run()
diff --git a/xos/synchronizer/event_steps/__init__.py b/xos/synchronizer/event_steps/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/event_steps/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/xos/synchronizer/event_steps/cord_workflow_event_probe.py b/xos/synchronizer/event_steps/cord_workflow_event_probe.py
new file mode 100644
index 0000000..7eecf24
--- /dev/null
+++ b/xos/synchronizer/event_steps/cord_workflow_event_probe.py
@@ -0,0 +1,72 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+from xossynchronizer.event_steps.eventstep import EventStep
+from cord_workflow_controller_client.probe import Probe
+
+
+class CORDWorkflowEventProbe(EventStep):
+    # topics = ["onu.events", "dhcp.events", "authentication.events"]
+    topics = ['*.events']
+    technology = 'kafka'
+    controller_url = 'http://controller:3030'
+    retry_conn_max = 3
+
+    def __init__(self, *args, **kwargs):
+        super(CORDWorkflowEventProbe, self).__init__(*args, **kwargs)
+
+        self.connected = False
+        self.retry = 0
+        self.connect()
+
+    def connect(self):
+        if not self.connected:
+            if self.retry > self.retry_conn_max:
+                self.log.info(
+                    'Could not connect to Workflow Controller (%s)...' %
+                    self.controller_url
+                )
+                self.probe = None
+                self.connected = False
+            else:
+                try:
+                    self.log.info(
+                        'Connecting to Workflow Controller (%s)...' %
+                        self.controller_url
+                    )
+
+                    self.probe = Probe(logger=self.log)
+                    self.probe.connect(self.controller_url)
+                    self.connected = True
+                    self.retry = 0
+                except Exception:
+                    self.probe = None
+                    self.connected = False
+                    self.retry += 1
+
+    def process_event(self, event):
+        if not self.connected:
+            self.connect()
+
+        if self.connected:
+            topic = event.topic
+            # event is in json format
+            message = json.loads(event.value)
+
+            self.log.info('Emitting an event (%s - %s)...' % (topic, message))
+            self.probe.emit_event(topic, message)
+            self.log.info('Emitted an event (%s - %s)...' % (topic, message))
+        else:
+            self.log.info('Skip emitting an event (%s - %s)...' % (topic, message))
diff --git a/xos/synchronizer/migrations/__init__.py b/xos/synchronizer/migrations/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/migrations/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/xos/synchronizer/model_policies/__init__.py b/xos/synchronizer/model_policies/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/model_policies/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/xos/synchronizer/model_policies/cord_workflow_model_event_probe.py b/xos/synchronizer/model_policies/cord_workflow_model_event_probe.py
new file mode 100644
index 0000000..06665de
--- /dev/null
+++ b/xos/synchronizer/model_policies/cord_workflow_model_event_probe.py
@@ -0,0 +1,96 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xossynchronizer.model_policies.policy import Policy
+from cord_workflow_controller_client.probe import Probe
+import os
+import sys
+
+sync_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
+sys.path.append(sync_path)
+
+controller_url = 'http://controller:3030'
+probe = None
+retry_conn_max = 3
+connected = False
+retry = 0
+
+
+def connect():
+    if not connected:
+        global probe, connected, retry
+        if retry > retry_conn_max:
+            probe = None
+            connected = False
+        else:
+            try:
+                probe = Probe()
+                probe.connect(controller_url)
+                connected = True
+                retry = 0
+            except Exception:
+                probe = None
+                connected = False
+                retry += 1
+
+
+def emit_helper(instance, event_type):
+    topic = 'datamodel.%s' % instance.model_name
+    message = {
+        'event_type': event_type
+    }
+
+    if not connected:
+        connect()
+
+    if connected:
+        if 'log' in instance:
+            instance.log.info('Emitting an event (%s - %s)...' % (topic, message))
+
+        if probe:
+            probe.emit_event(topic, message)
+
+        if 'log' in instance:
+            instance.log.info('Emitted an event (%s - %s)...' % (topic, message))
+    else:
+        if 'log' in instance:
+            instance.log.info('Skip emitting an event (%s - %s)...' % (topic, message))
+
+
+class CORDWorkflowModelEventProbe_ATTSI(Policy):
+    # TODO: NEED TO ALLOW MULTI-SUBSCRIPTION OF MODEL UPDATE EVENTS AT A LOWER LEVEL
+    # TO ELIMINATE CREATION OF A CLASS PER MODEL
+    model_name = "AttWorkflowDriverServiceInstance"
+
+    def handle_create(self, instance):
+        emit_helper(self, 'create')
+
+    def handle_update(self, instance):
+        emit_helper(self, 'update')
+
+    def handle_delete(self, instance):
+        emit_helper(self, 'delete')
+
+
+class CORDWorkflowModelEventProbe_ATTWL(Policy):
+    model_name = "AttWorkflowDriverWhiteListEntry"
+
+    def handle_create(self, instance):
+        emit_helper(self, 'create')
+
+    def handle_update(self, instance):
+        emit_helper(self, 'update')
+
+    def handle_delete(self, instance):
+        emit_helper(self, 'delete')
diff --git a/xos/synchronizer/test_config.yaml b/xos/synchronizer/test_config.yaml
new file mode 100644
index 0000000..c6263fe
--- /dev/null
+++ b/xos/synchronizer/test_config.yaml
@@ -0,0 +1,30 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+name: test-cord-workflow-probe
+accessor:
+  username: xosadmin@opencord.org
+  password: "sample"
+  kind: "testframework"
+logging:
+  version: 1
+  handlers:
+    console:
+      class: logging.StreamHandler
+  loggers:
+    'multistructlog':
+      handlers:
+          - console
+#      level: DEBUG
\ No newline at end of file
diff --git a/xos/unittest.cfg b/xos/unittest.cfg
new file mode 100644
index 0000000..4c43fef
--- /dev/null
+++ b/xos/unittest.cfg
@@ -0,0 +1,12 @@
+[unittest]
+plugins=nose2.plugins.junitxml
+code-directories=synchronizer
+                 model_policies
+                 steps
+                 pull_steps
+                 event_steps
+
+[coverage]
+always-on = True
+coverage = synchronizer
+coverage-report = term, html, xml
\ No newline at end of file