Add MANO project role support 02/1402/1
authorPhilip Joseph <philip.joseph@riftio.com>
Wed, 29 Mar 2017 11:41:32 +0000 (17:11 +0530)
committerPhilip Joseph <philip.joseph@riftio.com>
Wed, 29 Mar 2017 18:29:31 +0000 (23:59 +0530)
Signed-off-by: Philip Joseph <philip.joseph@riftio.com>
19 files changed:
BUILD.sh
CMakeLists.txt
common/python/rift/mano/utils/project.py
models/plugins/yang/CMakeLists.txt
models/plugins/yang/rw-project-mano.yang [deleted file]
rwlaunchpad/plugins/rwnsm/rift/tasklets/rwnsmtasklet/rwnsmtasklet.py
rwlaunchpad/plugins/rwvnfm/rift/tasklets/rwvnfmtasklet/rwvnfmtasklet.py
rwlaunchpad/test/launchpad.py
rwprojectmano/CMakeLists.txt [new file with mode: 0644]
rwprojectmano/plugins/CMakeLists.txt [new file with mode: 0644]
rwprojectmano/plugins/rwprojectmano/CMakeLists.txt [new file with mode: 0644]
rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/__init__.py [new file with mode: 0644]
rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/projectmano.py [new file with mode: 0644]
rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/rolesmano.py [new file with mode: 0644]
rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/tasklet.py [new file with mode: 0644]
rwprojectmano/plugins/rwprojectmano/rwprojectmano.py [new file with mode: 0755]
rwprojectmano/plugins/yang/CMakeLists.txt [new file with mode: 0644]
rwprojectmano/plugins/yang/Makefile [new file with mode: 0644]
rwprojectmano/plugins/yang/rw-project-mano.yang [new file with mode: 0644]

index 49e515b..94c8e21 100755 (executable)
--- a/BUILD.sh
+++ b/BUILD.sh
@@ -114,10 +114,10 @@ fi
 
 if [[ $PLATFORM == ub16 ]]; then
     PLATFORM_REPOSITORY=${1:-OSM}
-    PLATFORM_VERSION=${2:-4.99.1.1.58423}
+    PLATFORM_VERSION=${2:-4.99.1.1.58887}
 elif [[ $PLATFORM == fc20 ]]; then
     PLATFORM_REPOSITORY=${1:-OSM}  # change to OSM when published
-    PLATFORM_VERSION=${2:-4.99.1.1.58423}
+    PLATFORM_VERSION=${2:-4.99.1.1.58887}
 else
     echo "Internal error: unknown platform $PLATFORM"
     exit 1
index ae622a3..ddc62d9 100644 (file)
@@ -54,6 +54,7 @@ set(subdirs
   rwmon
   rwcm
   rwlaunchpad
+  rwprojectmano
   )
 
 if (NOT RIFT_AGENT_BUILD STREQUAL "XML_ONLY")
index de495e9..2609519 100644 (file)
@@ -398,11 +398,18 @@ class ProjectConfigCallbacks(object):
 class ProjectDtsHandler(object):
     XPATH = "C,{}/project-config".format(XPATH)
 
-    def __init__(self, dts, log, callbacks):
+    def __init__(self, dts, log, callbacks, sub_config=True):
         self._dts = dts
         self._log = log
         self._callbacks = callbacks
 
+        if sub_config:
+            self.xpath = ProjectDtsHandler.XPATH
+            self._key = 'name_ref'
+        else:
+            self.xpath = "C,{}".format(XPATH)
+            self._key = 'name'
+
         self.reg = None
         self.projects = []
 
@@ -446,20 +453,29 @@ class ProjectDtsHandler(object):
         if name in self.projects:
             pass
         else:
-            self.log.error("Unrecognized project: {}".
-                           format(name))
+            self.add_project(name)
 
     def register(self):
+        def on_init(acg, xact, scratch):
+            self._log.debug("on_init")
+            scratch["projects"] = {
+                "added": [],
+                "deleted": [],
+                "updated": [],
+            }
+            return scratch
+
         @asyncio.coroutine
         def apply_config(dts, acg, xact, action, scratch):
-            self._log.debug("Got project apply config (xact: %s) (action: %s)", xact, action)
+            self._log.debug("Got project apply config (xact: %s) (action: %s): %s",
+                            xact, action, scratch)
 
             if xact.xact is None:
                 if action == rwdts.AppconfAction.INSTALL:
                     curr_cfg = self._reg.elements
                     for cfg in curr_cfg:
                         self._log.debug("Project being re-added after restart.")
-                        self.add_project(cfg)
+                        self.add_project(cfg.name_ref)
                 else:
                     # When RIFT first comes up, an INSTALL is called with the current config
                     # Since confd doesn't actally persist data this never has any data so
@@ -468,23 +484,33 @@ class ProjectDtsHandler(object):
 
                 return
 
-            add_cfgs, delete_cfgs, update_cfgs = get_add_delete_update_cfgs(
-                    dts_member_reg=self._reg,
-                    xact=xact,
-                    key_name="name_ref",
-                    )
+            try:
+                add_cfgs = scratch["projects"]["added"]
+            except KeyError:
+                add_cfgs = []
+
+            try:
+                del_cfgs = scratch["projects"]["deleted"]
+            except KeyError:
+                del_cfgs = []
+
+            try:
+                update_cfgs = scratch["projects"]["updated"]
+            except KeyError:
+                update_cfgs = []
+
 
             # Handle Deletes
-            for cfg in delete_cfgs:
-                self.delete_project(cfg.name_ref)
+            for name in del_cfgs:
+                self.delete_project(name)
 
             # Handle Adds
-            for cfg in add_cfgs:
-                self.add_project(cfg.name_ref)
+            for name, msg in add_cfgs:
+                self.add_project(name)
 
             # Handle Updates
-            for cfg in update_cfgs:
-                self.update_project(cfg.name_ref)
+            for name, msg in update_cfgs:
+                self.update_project(name)
 
             return RwTypes.RwStatus.SUCCESS
 
@@ -493,38 +519,29 @@ class ProjectDtsHandler(object):
             """ Prepare callback from DTS for Project """
 
             action = xact_info.query_action
-            # xpath = ks_path.to_xpath(RwProjectYang.get_schema())
-            # name = ManoProject.from_xpath(xpath, self._log)
-            # if not name:
-            #     self._log.error("Did not find the project name in ks: {}".
-            #                     format(xpath))
-            #     xact_info.respond_xpath(rwdts.XactRspCode.NACK)
-            #     return
-
-            # if name != msg.name_ref:
-            #     self._log.error("The project name {} did not match the name {} in config".
-            #                     format(name, msg.name_ref))
-            # projects = scratch.setdefault('projects', {
-            #     'create': [],
-            #     'update': [],
-            #     'delete': [],
-            # })
-
-            # self._log.error("prepare msg type {}".format(type(msg)))
-            name = msg.name_ref
+            xpath = ks_path.to_xpath(RwProjectManoYang.get_schema())
+            self._log.debug("Project xpath: {}".format(xpath))
+            name = ManoProject.from_xpath(xpath, self._log)
 
             self._log.debug("Project %s on_prepare config received (action: %s): %s",
                             name, xact_info.query_action, msg)
 
-            if action in [rwdts.QueryAction.CREATE, rwdts.QueryAction.UPDATE]:
+            if action == rwdts.QueryAction.CREATE:
                 if name in self.projects:
                     self._log.debug("Project {} already exists. Ignore request".
                                     format(name))
+                else:
+                    yield from self._callbacks.on_add_prepare(name)
+                    scratch["projects"]["added"].append((name, msg))
 
+            elif action == rwdts.QueryAction.UPDATE:
+                if name in self.projects:
+                    scratch["projects"]["updated"].append(name, msg)
                 else:
                     self._log.debug("Project {}: Invoking on_prepare add request".
                                     format(name))
                     yield from self._callbacks.on_add_prepare(name)
+                    scratch["projects"]["added"].append((name, msg))
 
 
             elif action == rwdts.QueryAction.DELETE:
@@ -538,6 +555,9 @@ class ProjectDtsHandler(object):
                             self._log.error("Project {} should not be deleted".
                                             format(name))
                             xact_info.respond_xpath(rwdts.XactRspCode.NACK)
+                            return
+
+                        scratch["projects"]["deleted"].append(name)
                     else:
                         self._log.warning("Delete on unknown project: {}".
                                           format(name))
@@ -555,7 +575,7 @@ class ProjectDtsHandler(object):
 
         acg_handler = rift.tasklets.AppConfGroup.Handler(
                         on_apply=apply_config,
-                        )
+                        on_init=on_init)
 
         with self._dts.appconf_group_create(acg_handler) as acg:
             self._reg = acg.register(
@@ -564,6 +584,7 @@ class ProjectDtsHandler(object):
                     on_prepare=on_prepare,
                     )
 
+
 class ProjectHandler(object):
     def __init__(self, tasklet, project_class, **kw):
         self._tasklet = tasklet
@@ -629,7 +650,7 @@ class ProjectHandler(object):
                     self._class(name, self._tasklet, **(self._kw))
         except Exception as e:
             self._log.exception("Project {} create for {} failed: {}".
-                                formatname, self._get_tasklet_name(), e())
+                                format(name, self._get_tasklet_name(), e))
 
         try:
             yield from self._get_project(name).register()
index f31fa3c..998ecb2 100644 (file)
@@ -37,16 +37,6 @@ set(source_yang_files
   vnffgd.yang
   )
 
-rift_add_yang_target(
-  TARGET rwprojectmano_yang
-  YANG_FILES
-    rw-project-mano.yang
-  GIR_PATHS ${CMAKE_CURRENT_BINARY_DIR}
-  COMPONENT ${PKG_LONG_NAME}
-  LIBRARIES
-    rw_project_yang_gen
-  )
-
 rift_add_yang_target(
   TARGET mano-types_yang
   YANG_FILES
diff --git a/models/plugins/yang/rw-project-mano.yang b/models/plugins/yang/rw-project-mano.yang
deleted file mode 100644 (file)
index 8cf0924..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- *
- *   Copyright 2017 RIFT.IO Inc
- *
- *   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.
- *
- */
-
-module rw-project-mano
-{
-  namespace "http://riftio.com/ns/riftware-1.0/rw-project-mano";
-  prefix "rw-project-mano";
-
-  import rw-pb-ext {
-    prefix "rw-pb-ext";
-  }
-
-  import rw-project {
-    prefix "rw-project";
-  }
-
-  revision 2017-03-08 {
-    description
-      "Initial revision. This YANG file defines the
-       MANO extentions for project based tenancy";
-    reference
-      "Derived from earlier versions of base YANG files";
-  }
-
-  augment /rw-project:project/rw-project:project-config {
-    leaf name-ref {
-      type leafref {
-        path "../../rw-project:name";
-      }
-    }
-  }
-}
index 804439f..e739684 100755 (executable)
@@ -1086,7 +1086,7 @@ class VirtualNetworkFunctionRecord(object):
                 for vnfd_cp in vlr.vld_msg.vnfd_connection_point_ref:
                     if (vnfd_cp.vnfd_id_ref == self._vnfd.id and
                             vnfd_cp.vnfd_connection_point_ref == conn.name and
-                            vnfd_cp.member_vnf_index_ref == str(self.member_vnf_index) and
+                            vnfd_cp.member_vnf_index_ref == self.member_vnf_index and
                              vlr.cloud_account_name == self.cloud_account_name):
                         self._log.debug("Found VLR for cp_name:%s and vnf-index:%d",
                                         conn.name, self.member_vnf_index)
index 955f566..b198dd8 100755 (executable)
@@ -2025,8 +2025,9 @@ class VnfdDtsHandler(object):
         @asyncio.coroutine
         def on_prepare(dts, acg, xact, xact_info, ks_path, msg, scratch):
             """ on prepare callback """
-            self._log.debug("Got on prepare for VNFD (path: %s) (action: %s)",
-                            ks_path.to_xpath(RwVnfmYang.get_schema()), msg)
+            self._log.debug("Got on prepare for VNFD (path: %s) (action: %s) (msg: %s)",
+                            ks_path.to_xpath(RwVnfmYang.get_schema()),
+                            xact_info.query_action, msg)
             fref = ProtobufC.FieldReference.alloc()
             fref.goto_whole_message(msg.to_pbcm())
 
index c299311..b73c500 100755 (executable)
@@ -370,6 +370,34 @@ class ConfigManagerTasklet(rift.vcs.core.Tasklet):
     plugin_directory = ClassProperty('./usr/lib/rift/plugins/rwconmantasklet')
     plugin_name = ClassProperty('rwconmantasklet')
 
+
+class ProjectMgrManoTasklet(rift.vcs.core.Tasklet):
+    """
+    This class represents a Resource Manager tasklet.
+    """
+
+    def __init__(self, name='Project-Manager-Mano', uid=None,
+                 config_ready=True,
+                 recovery_action=core.RecoveryType.FAILCRITICAL.value,
+                 data_storetype=core.DataStore.NOSTORE.value,
+                 ):
+        """
+        Creates a ProjectMgrManoTasklet object.
+
+        Arguments:
+            name  - the name of the tasklet
+            uid   - a unique identifier
+        """
+        super(ProjectMgrManoTasklet, self).__init__(name=name, uid=uid,
+                                                    config_ready=config_ready,
+                                                    recovery_action=recovery_action,
+                                                    data_storetype=data_storetype,
+        )
+
+    plugin_directory = ClassProperty('./usr/lib/rift/plugins/rwprojectmano')
+    plugin_name = ClassProperty('rwprojectmano')
+
+
 class PackageManagerTasklet(rift.vcs.core.Tasklet):
     """
     This class represents a Resource Manager tasklet.
@@ -427,6 +455,7 @@ class Demo(rift.vcs.demo.Demo):
             rift.vcs.uAgentTasklet(),
             rift.vcs.IdentityManagerTasklet(),
             rift.vcs.ProjectManagerTasklet(),
+            ProjectMgrManoTasklet(),
             rift.vcs.Launchpad(),
             ]
 
@@ -455,6 +484,7 @@ class Demo(rift.vcs.demo.Demo):
               AutoscalerTasklet(recovery_action=core.RecoveryType.RESTART.value, data_storetype=datastore),
               PackageManagerTasklet(recovery_action=core.RecoveryType.RESTART.value, data_storetype=datastore),
               StagingManagerTasklet(recovery_action=core.RecoveryType.RESTART.value, data_storetype=datastore),
+              ProjectMgrManoTasklet(recovery_action=core.RecoveryType.RESTART.value, data_storetype=datastore),
             ]
 
         if not mgmt_ip_list or len(mgmt_ip_list) == 0:
diff --git a/rwprojectmano/CMakeLists.txt b/rwprojectmano/CMakeLists.txt
new file mode 100644 (file)
index 0000000..78e225f
--- /dev/null
@@ -0,0 +1,28 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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.
+#
+#
+
+cmake_minimum_required(VERSION 2.8)
+
+set(PKG_NAME rwprojectmano)
+set(PKG_VERSION 1.0)
+set(PKG_RELEASE 1)
+set(PKG_LONG_NAME ${PKG_NAME}-${PKG_VERSION})
+
+set(subdirs
+  plugins
+  )
+rift_add_subdirs(SUBDIR_LIST ${subdirs})
diff --git a/rwprojectmano/plugins/CMakeLists.txt b/rwprojectmano/plugins/CMakeLists.txt
new file mode 100644 (file)
index 0000000..21c8e94
--- /dev/null
@@ -0,0 +1,24 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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.
+#
+#
+
+cmake_minimum_required(VERSION 2.8)
+
+set(subdirs
+  yang
+  rwprojectmano
+  )
+rift_add_subdirs(SUBDIR_LIST ${subdirs})
diff --git a/rwprojectmano/plugins/rwprojectmano/CMakeLists.txt b/rwprojectmano/plugins/rwprojectmano/CMakeLists.txt
new file mode 100644 (file)
index 0000000..16b4b76
--- /dev/null
@@ -0,0 +1,40 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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.
+#
+
+cmake_minimum_required(VERSION 2.8)
+
+include(rift_plugin)
+
+set(TASKLET_NAME rwprojectmano)
+
+##
+# This function creates an install target for the plugin artifacts
+##
+rift_install_python_plugin(
+  ${TASKLET_NAME} ${TASKLET_NAME}.py)
+
+# Workaround RIFT-6485 - rpmbuild defaults to python2 for
+# anything not in a site-packages directory so we have to
+# install the plugin implementation in site-packages and then
+# import it from the actual plugin.
+rift_python_install_tree(
+  FILES
+    rift/tasklets/${TASKLET_NAME}/__init__.py
+    rift/tasklets/${TASKLET_NAME}/tasklet.py
+    rift/tasklets/${TASKLET_NAME}/projectmano.py
+    rift/tasklets/${TASKLET_NAME}/rolesmano.py
+  COMPONENT ${PKG_LONG_NAME}
+  PYTHON3_ONLY)
diff --git a/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/__init__.py b/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/__init__.py
new file mode 100644 (file)
index 0000000..24d7753
--- /dev/null
@@ -0,0 +1 @@
+from .tasklet import ProjectMgrManoTasklet
diff --git a/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/projectmano.py b/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/projectmano.py
new file mode 100644 (file)
index 0000000..406c0a6
--- /dev/null
@@ -0,0 +1,278 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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.
+#
+
+"""
+Project Manager tasklet is responsible for managing the Projects
+configurations required for Role Based Access Control feature.
+"""
+
+
+import asyncio
+
+import gi
+gi.require_version('RwDts', '1.0')
+from gi.repository import (
+    RwDts as rwdts,
+    ProtobufC,
+    RwTypes,
+)
+
+import rift.tasklets
+from rift.mano.utils.project import (
+    NS_PROJECT,
+    get_add_delete_update_cfgs,
+    ProjectConfigCallbacks,
+)
+
+
+class ProjectDtsHandler(object):
+    XPATH = "C,/{}".format(NS_PROJECT)
+
+    def __init__(self, dts, log, callbacks):
+        self._dts = dts
+        self._log = log
+        self._callbacks = callbacks
+
+        self.reg = None
+        self.projects = []
+
+    @property
+    def log(self):
+        return self._log
+
+    @property
+    def dts(self):
+        return self._dts
+
+    def add_project(self, cfg):
+        name = cfg.name
+        self.log.info("Adding project: {}".format(name))
+
+        if name not in self.projects:
+            self._callbacks.on_add_apply(name, cfg)
+            self.projects.append(name)
+        else:
+            self.log.error("Project already present: {}".
+                           format(name))
+
+    def delete_project(self, name):
+        self._log.info("Deleting project: {}".format(name))
+        if name in self.projects:
+            self._callbacks.on_delete_apply(name)
+            self.projects.remove(name)
+        else:
+            self.log.error("Unrecognized project: {}".
+                           format(name))
+
+    def update_project(self, cfg):
+        """ Update an existing project
+
+        Currently, we do not take any action on MANO for this,
+        so no callbacks are defined
+
+        Arguments:
+            msg - The project config message
+        """
+        name = cfg.name
+        self._log.info("Updating project: {}".format(name))
+        if name in self.projects:
+            pass
+        else:
+            self.log.error("Unrecognized project: {}".
+                           format(name))
+
+    def register(self):
+        @asyncio.coroutine
+        def apply_config(dts, acg, xact, action, scratch):
+            self._log.debug("Got project apply config (xact: %s) (action: %s)", xact, action)
+
+            if xact.xact is None:
+                if action == rwdts.AppconfAction.INSTALL:
+                    curr_cfg = self._reg.elements
+                    for cfg in curr_cfg:
+                        self._log.debug("Project being re-added after restart.")
+                        self.add_project(cfg.name)
+                else:
+                    # When RIFT first comes up, an INSTALL is called with the current config
+                    # Since confd doesn't actally persist data this never has any data so
+                    # skip this for now.
+                    self._log.debug("No xact handle.  Skipping apply config")
+
+                return
+
+            add_cfgs, delete_cfgs, update_cfgs = get_add_delete_update_cfgs(
+                    dts_member_reg=self._reg,
+                    xact=xact,
+                    key_name="name",
+                    )
+
+            # Handle Deletes
+            for cfg in delete_cfgs:
+                self.delete_project(cfg.name)
+
+            # Handle Adds
+            for cfg in add_cfgs:
+                self.add_project(cfg)
+
+            # Handle Updates
+            for cfg in update_cfgs:
+                self.update_project(cfg)
+
+            return RwTypes.RwStatus.SUCCESS
+
+        @asyncio.coroutine
+        def on_prepare(dts, acg, xact, xact_info, ks_path, msg, scratch):
+            """ Prepare callback from DTS for Project """
+
+            action = xact_info.query_action
+            name = msg.name
+
+            self._log.debug("Project %s on_prepare config received (action: %s): %s",
+                            name, xact_info.query_action, msg)
+
+            if action in [rwdts.QueryAction.CREATE, rwdts.QueryAction.UPDATE]:
+                if name in self.projects:
+                    self._log.debug("Project {} already exists. Ignore request".
+                                    format(name))
+
+                else:
+                    self._log.debug("Project {}: Invoking on_prepare add request".
+                                    format(name))
+                    yield from self._callbacks.on_add_prepare(name, msg)
+
+
+            elif action == rwdts.QueryAction.DELETE:
+                # Check if the entire project got deleted
+                fref = ProtobufC.FieldReference.alloc()
+                fref.goto_whole_message(msg.to_pbcm())
+                if fref.is_field_deleted():
+                    if name in self.projects:
+                        rc = yield from self._callbacks.on_delete_prepare(name)
+                        if not rc:
+                            self._log.error("Project {} should not be deleted".
+                                            format(name))
+                            xact_info.respond_xpath(rwdts.XactRspCode.NACK)
+                            return
+                    else:
+                        self._log.warning("Delete on unknown project: {}".
+                                          format(name))
+
+            else:
+                self._log.error("Action (%s) NOT SUPPORTED", action)
+                xact_info.respond_xpath(rwdts.XactRspCode.NACK)
+                return
+
+            xact_info.respond_xpath(rwdts.XactRspCode.ACK)
+
+        self._log.debug("Registering for project config using xpath: %s",
+                        ProjectDtsHandler.XPATH)
+
+        acg_handler = rift.tasklets.AppConfGroup.Handler(
+            on_apply=apply_config,
+        )
+
+        with self._dts.appconf_group_create(acg_handler) as acg:
+            self._reg = acg.register(
+                xpath=ProjectDtsHandler.XPATH,
+                flags=rwdts.Flag.SUBSCRIBER | rwdts.Flag.DELTA_READY | rwdts.Flag.CACHE,
+                on_prepare=on_prepare,
+            )
+
+
+class ProjectHandler(object):
+    def __init__(self, tasklet, project_class):
+        self._tasklet = tasklet
+        self._log = tasklet.log
+        self._log_hdl = tasklet.log_hdl
+        self._dts = tasklet.dts
+        self._loop = tasklet.loop
+        self._class = project_class
+
+        self._log.debug("Creating project config handler")
+        self.project_cfg_handler = ProjectDtsHandler(
+            self._dts, self._log,
+            ProjectConfigCallbacks(
+                on_add_apply=self.on_project_added,
+                on_add_prepare=self.on_add_prepare,
+                on_delete_apply=self.on_project_deleted,
+                on_delete_prepare=self.on_delete_prepare,
+            )
+        )
+
+    def _get_tasklet_name(self):
+        return self._tasklet.tasklet_info.instance_name
+
+    def _get_project(self, name):
+        try:
+            proj = self._tasklet.projects[name]
+        except Exception as e:
+            self._log.exception("Project {} ({})not found for tasklet {}: {}".
+                                format(name, list(self._tasklet.projects.keys()),
+                                       self._get_tasklet_name(), e))
+            raise e
+
+        return proj
+
+    def on_project_deleted(self, name):
+        self._log.debug("Project {} deleted".format(name))
+        try:
+            self._get_project(name).deregister()
+        except Exception as e:
+            self._log.exception("Project {} deregister for {} failed: {}".
+                                format(name, self._get_tasklet_name(), e))
+
+        try:
+            proj = self._tasklet.projects.pop(name)
+            del proj
+        except Exception as e:
+            self._log.exception("Project {} delete for {} failed: {}".
+                                format(name, self._get_tasklet_name(), e))
+
+    def on_project_added(self, name, cfg):
+        self._log.debug("Project {} added to tasklet {}".
+                        format(name, self._get_tasklet_name()))
+        self._get_project(name)._apply = True
+
+    @asyncio.coroutine
+    def on_add_prepare(self, name, msg):
+        self._log.debug("Project {} to be added to {}".
+                        format(name, self._get_tasklet_name()))
+
+        try:
+            self._tasklet.projects[name] = \
+                    self._class(name, self._tasklet)
+        except Exception as e:
+            self._log.exception("Project {} create for {} failed: {}".
+                                format(name, self._get_tasklet_name(), e))
+
+        try:
+            yield from self._get_project(name).register()
+        except Exception as e:
+            self._log.exception("Project {} register for tasklet {} failed: {}".
+                                format(name, self._get_tasklet_name(), e))
+
+        self._log.debug("Project {} added to {}".
+                        format(name, self._get_tasklet_name()))
+
+    @asyncio.coroutine
+    def on_delete_prepare(self, name):
+        self._log.debug("Project {} being deleted for tasklet {}".
+                        format(name, self._get_tasklet_name()))
+        rc = yield from self._get_project(name).delete_prepare()
+        return rc
+
+    def register(self):
+        self.project_cfg_handler.register()
diff --git a/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/rolesmano.py b/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/rolesmano.py
new file mode 100644 (file)
index 0000000..ea3674a
--- /dev/null
@@ -0,0 +1,375 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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.
+#
+
+"""
+Project Manager tasklet is responsible for managing the Projects
+configurations required for Role Based Access Control feature.
+"""
+
+
+import asyncio
+from enum import Enum
+
+import gi
+gi.require_version('RwDts', '1.0')
+gi.require_version('RwRbacInternalYang', '1.0')
+from gi.repository import (
+    RwDts as rwdts,
+    ProtobufC,
+    RwTypes,
+    RwRbacInternalYang,
+)
+
+import rift.tasklets
+#TODO: Fix once merged to latest platform
+from rift.tasklets.rwproject.project import (
+    StateMachine,
+    User,
+    UserState,
+    RoleKeys,
+    RoleKeysUsers,
+)
+from rift.mano.utils.project import (
+    NS_PROJECT,
+    get_add_delete_update_cfgs,
+)
+
+
+MANO_PROJECT_ROLES = [
+            'rw-project-mano:mano-oper',
+            'rw-project-mano:mano-admin',
+]
+
+
+class ProjectConfigSubscriber(object):
+    """Config subscriber for rw-user config"""
+
+    def __init__(self, project):
+        self.project_name = project.name
+        self._log = project.log
+        self._dts = project.dts
+
+        self.users = {}
+        self.pub = RoleConfigPublisher(project)
+
+    def get_xpath(self):
+        return "C,/{}[name='{}']/project-config/user".format(NS_PROJECT, self.project_name)
+
+
+    def role_inst(self, role, keys=None):
+        if not keys:
+            keys = self.project_name
+
+        r = RoleKeys()
+        r.role = role.role
+        r.keys = keys
+        return r
+
+    def delete_user(self, cfg):
+        user = User().pb(cfg)
+        if user.key in self.users:
+            roles = self.users[user.key]
+            for role_key in list(roles):
+                self.delete_role(user, role_key)
+            self.users.pop(user.key)
+
+    def update_user(self, cfg):
+        user = User().pb(cfg)
+        cfg_roles = {}
+        for cfg_role in cfg.mano_role:
+            r = self.role_inst(cfg_role)
+            cfg_roles[r.key] = r
+
+        if not user.key in self.users:
+            self.users[user.key] = set()
+        else:
+            #Check if any roles are deleted for the user
+            for role_key in list(self.users[user.key]):
+                    if role_key not in cfg_roles:
+                        self.delete_role(user, role_key)
+
+        for role_key in cfg_roles.keys():
+            if role_key not in self.users[user.key]:
+                self.update_role(user, cfg_roles[role_key])
+
+    def delete_role(self, user, role_key):
+        user_key = user.key
+
+        try:
+            roles = self.users[user_key]
+        except KeyError:
+            roles = set()
+            self.users[user.key] = roles
+
+        if role_key in roles:
+            roles.remove(role_key)
+            self.pub.delete_role(role_key, user_key)
+
+    def update_role(self, user, role):
+        user_key = user.key
+
+        try:
+            roles = self.users[user.key]
+        except KeyError:
+            roles = set()
+            self.users[user_key] = roles
+
+        role_key = role.key
+
+        if not role_key in roles:
+            roles.add(role_key)
+            self.pub.add_update_role(role_key, user_key)
+
+    @asyncio.coroutine
+    def register(self):
+        @asyncio.coroutine
+        def apply_config(dts, acg, xact, action, scratch):
+            self._log.debug("Got user apply config (xact: %s) (action: %s)",
+                            xact, action)
+
+            if xact.xact is None:
+                if action == rwdts.AppconfAction.INSTALL:
+                    curr_cfg = self._reg.elements
+                    for cfg in curr_cfg:
+                        self._log.debug("Project being re-added after restart.")
+                        self.add_user(cfg)
+                else:
+                    # When RIFT first comes up, an INSTALL is called with the current config
+                    # Since confd doesn't actally persist data this never has any data so
+                    # skip this for now.
+                    self._log.debug("No xact handle.  Skipping apply config")
+
+                return
+
+            # TODO: There is user-name and user-domain as keys. Need to fix
+            # this
+            add_cfgs, delete_cfgs, update_cfgs = get_add_delete_update_cfgs(
+                    dts_member_reg=self._reg,
+                    xact=xact,
+                    key_name="user_name",
+                    )
+
+            self._log.debug("Added: {}, Deleted: {}, Modified: {}".
+                            format(add_cfgs, delete_cfgs, update_cfgs))
+            # Handle Deletes
+            for cfg in delete_cfgs:
+                self.delete_user(cfg)
+
+            # Handle Adds
+            for cfg in add_cfgs:
+                self.update_user(cfg)
+
+            # Handle Updates
+            for cfg in update_cfgs:
+                self.update_user(cfg)
+
+            return RwTypes.RwStatus.SUCCESS
+
+        @asyncio.coroutine
+        def on_prepare(dts, acg, xact, xact_info, ks_path, msg, scratch):
+            """ Prepare callback from DTS for Project """
+
+            action = xact_info.query_action
+
+            self._log.debug("Project %s on_prepare config received (action: %s): %s",
+                            self.project_name, xact_info.query_action, msg)
+
+            user = User().pb(msg)
+            if action in [rwdts.QueryAction.CREATE, rwdts.QueryAction.UPDATE]:
+                if user.key in self.users:
+                    self._log.debug("User {} update request".
+                                    format(user.key))
+
+                else:
+                    self._log.debug("User {}: on_prepare add request".
+                                    format(user.key))
+
+            elif action == rwdts.QueryAction.DELETE:
+                # Check if the user got deleted
+                fref = ProtobufC.FieldReference.alloc()
+                fref.goto_whole_message(msg.to_pbcm())
+                if fref.is_field_deleted():
+                    if user.key in self.users:
+                        self._log.debug("User {} being deleted".format(user.key))
+                    else:
+                        self._log.warning("Delete on unknown user: {}".
+                                          format(user.key))
+
+            else:
+                self._log.error("Action (%s) NOT SUPPORTED", action)
+                xact_info.respond_xpath(rwdts.XactRspCode.NACK)
+                return
+
+            xact_info.respond_xpath(rwdts.XactRspCode.ACK)
+
+        xpath = self.get_xpath()
+        self._log.debug("Registering for project config using xpath: %s",
+                        xpath,
+                        )
+
+        acg_handler = rift.tasklets.AppConfGroup.Handler(
+                        on_apply=apply_config,
+                        )
+
+        with self._dts.appconf_group_create(acg_handler) as acg:
+            self._reg = acg.register(
+                    xpath=xpath,
+                    flags=rwdts.Flag.SUBSCRIBER | rwdts.Flag.DELTA_READY | rwdts.Flag.CACHE,
+                    on_prepare=on_prepare,
+                    )
+
+        yield from self.pub.register()
+        self.pub.create_project_roles()
+
+    def deregister(self):
+        self._log.debug("De-registering DTS handler for project {}".
+                        format(self.project_name))
+        if self._reg:
+            self._reg.deregister()
+            self._reg = None
+
+        self.pub.deregister()
+
+
+class RoleConfigPublisher(rift.tasklets.DtsConfigPublisher):
+
+    def __init__(self, project):
+        super().__init__(project._tasklet)
+        self.project_name = project.name
+        self.rbac_int = RwRbacInternalYang.YangData_RwRbacInternal_RwRbacInternal()
+        self.roles = {}
+        self.proj_roles = MANO_PROJECT_ROLES
+        self.proj_roles_published = False
+
+    def get_xpath(self):
+        return "D,/rw-rbac-internal:rw-rbac-internal/rw-rbac-internal:role"
+
+    def role_xpath(self, role_key):
+        return "D,/rw-rbac-internal:rw-rbac-internal/rw-rbac-internal:role" + \
+            "[rw-rbac-internal:role='{}']".format(role_key[0]) + \
+            "[rw-rbac-internal:keys='{}']".format(role_key[1])
+
+    def role_user_xpath(self, role_key, user_key):
+        return self.role_xpath(role_key) + \
+            "/rw-rbac-internal:user" + \
+            "[rw-rbac-internal:user-name='{}']".format(user_key[1]) + \
+            "[rw-rbac-internal:user-domain='{}']".format(user_key[0])
+
+    def create_project_roles(self):
+        for name in self.proj_roles:
+            role = RoleKeys()
+            role.role = name
+            role.keys = self.project_name
+            self.create_project_role(role)
+
+    def create_project_role(self, role):
+        xpath = self.role_xpath(role.key)
+        pb_role = self.pb_role(role)
+        self._regh.update_element(xpath, pb_role)
+
+    def create_role(self, role_key, user_key):
+        return  RoleKeysUsers(role_key, user_key)
+
+    def pb_role(self, role):
+
+        pbRole = self.rbac_int.create_role()
+        pbRole.role = role.role
+        pbRole.keys = role.keys
+
+        return pbRole
+
+    def pb_role_user(self, role, user):
+
+        pbRole = self.rbac_int.create_role()
+        pbRole.role = role.role
+        pbRole.keys = role.keys
+
+        pbUser = pbRole.create_user()
+        pbUser.user_name = user.user_name
+        pbUser.user_domain = user.user_domain
+        pbUser.state_machine.state = user.state.name
+
+        pbRole.user.append(pbUser)
+
+        return pbRole
+
+    def add_update_role(self, role_key, user_key):
+        update = True
+        try:
+            role = self.roles[role_key]
+        except KeyError:
+            role = RoleKeysUsers(role_key)
+            self.roles[role_key] = role
+            update = False
+
+        try:
+            user = role.user(user_key)
+        except KeyError:
+            user = UserState(user_key)
+            role.add_user(user)
+            update = False
+
+        user.state = StateMachine.new
+
+        xpath = self.role_xpath(role_key)
+
+        pb_role_user = self.pb_role_user(role, user)
+        self.log.debug("add_update_role: xpath:{} pb_role:{}".format(xpath, pb_role_user))
+
+        self._regh.update_element(xpath, pb_role_user)
+
+    def delete_role(self, role_key, user_key):
+        try:
+            role = self.roles[role_key]
+            user = role.user(user_key)
+        except KeyError:
+            return
+
+        user.state = StateMachine.delete
+        xpath = self.role_xpath(role_key)
+        self.log.debug("deleting role: {} user: {} ".format(role_key, user_key))
+
+        pb_role = self.pb_role_user(role, user)
+        self._regh.update_element(xpath, pb_role)
+
+    def do_prepare(self, xact_info, action, ks_path, msg):
+        """Handle on_prepare.  To be overridden by Concreate Publisher Handler
+        """
+        self.log.debug("do_prepare: action: {}, path: {} ks_path, msg: {}".format(action, ks_path, msg))
+
+        xact_info.respond_xpath(rwdts.XactRspCode.ACK)
+
+        # TODO: See if we need this as this would be called in the platform also
+        # role_key = tuple([msg.role, msg.keys])
+
+        # state = msg.state_machine.state
+        # if state == 'init_done':
+        #     msg.state_machine.state = 'active'
+        #     xpath = self.role_xpath(role_key)
+        #     self._regh.update_element(xpath, msg)
+
+        # for user in msg.users:
+        #     user_key = tuple([user.user_domain, user.user_name])
+        #     state = user.state_machine.state
+        #     if state == 'init_done':
+        #         user.state_machine.state = 'active'
+        #         xpath = self.role_xpath(role_key)
+        #         self._regh.update_element(xpath, msg)
+
+    def deregister(self):
+        if self._regh:
+            self._regh.deregister()
+            self._regh = None
diff --git a/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/tasklet.py b/rwprojectmano/plugins/rwprojectmano/rift/tasklets/rwprojectmano/tasklet.py
new file mode 100644 (file)
index 0000000..eb71fa2
--- /dev/null
@@ -0,0 +1,160 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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.
+#
+
+"""
+Mano Project Manager tasklet is responsible for managing the Projects
+configurations required for Role Based Access Control feature.
+"""
+
+import asyncio
+
+import gi
+gi.require_version('RwDts', '1.0')
+gi.require_version('RwLog', '1.0')
+gi.require_version('RwProjectYang', '1.0')
+gi.require_version('RwProjectManoYang', '1.0')
+from gi.repository import (
+    RwDts as rwdts,
+    RwLog as rwlog,
+    RwProjectYang,
+    RwProjectManoYang,
+)
+
+import rift.tasklets
+
+from rift.mano.utils.project import (
+    ManoProject,
+    )
+
+from .projectmano import (
+    ProjectHandler,
+)
+
+from .rolesmano import (
+    RoleConfigPublisher,
+    ProjectConfigSubscriber,
+)
+
+
+class ProjectMgrManoProject(ManoProject):
+
+    def __init__(self, name, tasklet):
+        super(ProjectMgrManoProject, self).__init__(tasklet.log, name)
+        self.update(tasklet)
+
+        self.project_sub = ProjectConfigSubscriber(self)
+
+    @asyncio.coroutine
+    def register (self):
+        self._log.info("Initializing the ProjectMgrMano for %s", self.name)
+        yield from self.project_sub.register()
+
+    def deregister(self):
+        self._log.debug("De-register project %s", self.name)
+        self.project_sub.deregister()
+
+
+class ProjectMgrManoTasklet(rift.tasklets.Tasklet):
+    """Tasklet that manages the Project config
+    """
+    def __init__(self, *args, **kwargs):
+        """Constructs a ProjectManager tasklet"""
+        try:
+            super().__init__(*args, **kwargs)
+            self.rwlog.set_category("rw-mano-log")
+
+            self.projects = {}
+
+        except Exception as e:
+            self.log.exception(e)
+
+
+    def start(self):
+        """Callback that gets invoked when a Tasklet is started"""
+        super().start()
+        self.log.info("Starting Mano Project Manager Tasklet")
+
+        self.log.debug("Registering with dts")
+        self.dts = rift.tasklets.DTS(
+                self.tasklet_info,
+                RwProjectManoYang.get_schema(),
+                self.loop,
+                self.on_dts_state_change
+                )
+
+        self.log.debug("Created DTS Api Object: %s", self.dts)
+
+    def stop(self):
+        """Callback that gets invoked when Tasklet is stopped"""
+        try:
+            self.dts.deinit()
+        except Exception as e:
+            self.log.exception(e)
+
+    @asyncio.coroutine
+    def init(self):
+        """DTS Init state handler"""
+        try:
+            self.log.info("Registering for Project Config")
+            self.project_handler = ProjectHandler(self, ProjectMgrManoProject)
+
+            self.project_handler.register()
+
+        except Exception as e:
+            self.log.exception("Registering for project failed: {}".format(e))
+
+    @asyncio.coroutine
+    def run(self):
+        """DTS run state handler"""
+        pass
+
+    @asyncio.coroutine
+    def on_dts_state_change(self, state):
+        """Handle DTS state change
+
+        Take action according to current DTS state to transition application
+        into the corresponding application state
+
+        Arguments
+            state - current dts state
+
+        """
+        switch = {
+            rwdts.State.INIT: rwdts.State.REGN_COMPLETE,
+            rwdts.State.CONFIG: rwdts.State.RUN,
+        }
+
+        handlers = {
+            rwdts.State.INIT: self.init,
+            rwdts.State.RUN: self.run,
+        }
+
+        # Transition application to next state
+        handler = handlers.get(state, None)
+        if handler is not None:
+            yield from handler()
+
+        # Transition dts to next state
+        next_state = switch.get(state, None)
+        if next_state is not None:
+            self.dts.handle.set_state(next_state)
+
+    def config_ready(self):
+        """Subscription is complete and ready to start publishing."""
+        self.log.debug("Configuration Ready")
+
+
+# vim: ts=4 sw=4 et
diff --git a/rwprojectmano/plugins/rwprojectmano/rwprojectmano.py b/rwprojectmano/plugins/rwprojectmano/rwprojectmano.py
new file mode 100755 (executable)
index 0000000..ac7ac47
--- /dev/null
@@ -0,0 +1,22 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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 rift.tasklets.rwprojectmano
+
+class Tasklet(rift.tasklets.rwprojectmano.ProjectMgrManoTasklet):
+    pass
+
+# vim: sw=4
diff --git a/rwprojectmano/plugins/yang/CMakeLists.txt b/rwprojectmano/plugins/yang/CMakeLists.txt
new file mode 100644 (file)
index 0000000..00e5110
--- /dev/null
@@ -0,0 +1,27 @@
+#
+#   Copyright 2017 RIFT.IO Inc
+#
+#   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.
+#
+#
+
+rift_add_yang_target(
+  TARGET rwprojectmano_yang
+  YANG_FILES
+    rw-project-mano.yang
+  GIR_PATHS ${CMAKE_CURRENT_BINARY_DIR}
+  COMPONENT ${PKG_LONG_NAME}
+  LIBRARIES
+    rw_project_yang_gen
+  )
+
diff --git a/rwprojectmano/plugins/yang/Makefile b/rwprojectmano/plugins/yang/Makefile
new file mode 100644 (file)
index 0000000..2b691a8
--- /dev/null
@@ -0,0 +1,36 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+# Author(s): Tim Mortsolf
+# Creation Date: 11/25/2013
+# 
+
+##
+# Define a Makefile function: find_upwards(filename)
+#
+# Searches for a file of the given name in the directory ., .., ../.., ../../.., etc.,
+# until the file is found or the root directory is reached
+##
+find_upward = $(word 1, $(shell while [ `pwd` != / ] ; do find `pwd` -maxdepth 1 -name $1 ; cd .. ; done))
+
+##
+# Call find_upward("Makefile.top") to find the nearest upwards adjacent Makefile.top
+##
+makefile.top := $(call find_upward, "Makefile.top")
+
+##
+# If Makefile.top was found, then include it
+##
+include $(makefile.top)
diff --git a/rwprojectmano/plugins/yang/rw-project-mano.yang b/rwprojectmano/plugins/yang/rw-project-mano.yang
new file mode 100644 (file)
index 0000000..1369058
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ *
+ *   Copyright 2017 RIFT.IO Inc
+ *
+ *   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.
+ *
+ */
+
+module rw-project-mano
+{
+  namespace "http://riftio.com/ns/riftware-1.0/rw-project-mano";
+  prefix "rw-project-mano";
+
+  import rw-rbac-base {
+    prefix "rw-rbac-base";
+  }
+
+  import rw-pb-ext {
+    prefix "rw-pb-ext";
+  }
+
+  import rw-project {
+    prefix "rw-project";
+  }
+
+  import rw-rbac-internal {
+    prefix "rw-rbac-internal";
+  }
+
+  revision 2017-03-08 {
+    description
+      "Initial revision. This YANG file defines the
+       MANO extentions for project based tenancy";
+    reference
+      "Derived from earlier versions of base YANG files";
+  }
+
+  augment /rw-project:project/rw-project:project-config/rw-project:user {
+    description
+      "Configuration for MANO application-specific Roles.";
+
+    list mano-role {
+      description
+        "The list of MANO application-specific Roles the User has been
+        assigned, within the enclosing Project.";
+
+      key "role";
+      uses rw-rbac-base:simple-role;
+    }
+  }
+
+  augment /rw-project:project/rw-project:project-state/rw-project:user {
+    description
+      "The state for MANO application-specific Roles.";
+
+    list mano-role {
+      description
+      "The state of the MANO application-specific Role the User has
+      been assigned.";
+
+      key "role";
+      uses rw-rbac-base:simple-role;
+
+      leaf state {
+        description
+          "The assignment of a User to a Role may be an asynchronous
+          operation.  This value indicates whether the Role
+          assignment is active.  If the value is 'active', then the
+          assignment is complete and active.  Any other value
+          indicates that Role assignment is in a transitional or
+          failed state, as described in the value.";
+        type string;
+      }
+    }
+  }
+
+  augment /rw-project:project/rw-project:project-state {
+    description
+      "State for MANO application-specific Roles.";
+
+    list mano-role {
+      description
+        "The set of Roles that may be configured into
+        /rw-project:project/rw-project:project-config/rw-project:user/
+        rw-project-mano:mano-role/rw-project-mano:role.";
+
+      key "role";
+      uses rw-rbac-base:simple-role;
+
+      leaf description {
+        description
+          "A description of the Role.";
+        type string;
+      }
+    }
+  }
+}