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
rwmon
rwcm
rwlaunchpad
+ rwprojectmano
)
if (NOT RIFT_AGENT_BUILD STREQUAL "XML_ONLY")
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 = []
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
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
""" 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:
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))
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(
on_prepare=on_prepare,
)
+
class ProjectHandler(object):
def __init__(self, tasklet, project_class, **kw):
self._tasklet = tasklet
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()
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
+++ /dev/null
-/*
- *
- * 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";
- }
- }
- }
-}
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)
@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())
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.
rift.vcs.uAgentTasklet(),
rift.vcs.IdentityManagerTasklet(),
rift.vcs.ProjectManagerTasklet(),
+ ProjectMgrManoTasklet(),
rift.vcs.Launchpad(),
]
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:
--- /dev/null
+#
+# 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})
--- /dev/null
+#
+# 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})
--- /dev/null
+#
+# 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)
--- /dev/null
+from .tasklet import ProjectMgrManoTasklet
--- /dev/null
+#
+# 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()
--- /dev/null
+#
+# 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
--- /dev/null
+#
+# 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
--- /dev/null
+#
+# 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
--- /dev/null
+#
+# 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
+ )
+
--- /dev/null
+#
+# 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)
--- /dev/null
+/*
+ *
+ * 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;
+ }
+ }
+ }
+}