2 # Copyright 2017 RIFT.IO Inc
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
18 Project Manager tasklet is responsible for managing the Projects
19 configurations required for Role Based Access Control feature.
25 gi
.require_version('RwDts', '1.0')
26 gi
.require_version('RwProjectManoYang', '1.0')
27 from gi
.repository
import (
33 gi
.require_version('RwKeyspec', '1.0')
34 from gi
.repository
.RwKeyspec
import quoted_key
37 from rift
.mano
.utils
.project
import (
39 get_add_delete_update_cfgs
,
40 ProjectConfigCallbacks
,
44 MANO_PROJECT_ROLES
= [
45 { 'mano-role':"rw-project-mano:catalog-oper",
46 'description':("The catalog-oper Role has read permission to nsd-catalog "
47 "and vnfd-catalog under specific Projects, "
48 "as identified by /rw-project:project/rw-project:name. The "
49 "catatlog-oper Role may also have execute permission to specific "
50 "non-mutating RPCs. This Role is intended for read-only access to "
51 "catalogs under a specific project.") },
53 { 'mano-role':"rw-project-mano:catalog-admin",
54 'description':("The catalog-admin Role has full CRUDX permissions to vnfd and nsd "
55 "catalogs under specific Projects, as identified by "
56 "/rw-project:project/rw-project:name.") },
58 { 'mano-role':"rw-project-mano:lcm-oper",
59 'description':("The lcm-oper Role has read permission to the VL, VNF and NS "
60 "records within a Project. The lcm-oper Role may also have "
61 "execute permission to specific non-mutating RPCs.") },
63 { 'mano-role':"rw-project-mano:lcm-admin",
64 'description':("The lcm-admin Role has full CRUDX permissions to the VL, VNF "
65 "and NS records within a Project. The lcm-admin Role does "
66 "not provide general CRUDX permissions to the Project as a whole, "
67 "nor to the RIFT.ware platform in general.") },
69 { 'mano-role':"rw-project-mano:account-admin",
70 'description':("The account-admin Role has full CRUDX permissions to the VIM, SDN, VCA "
71 "and RO accounts within a Project. The account-admin Role does "
72 "not provide general CRUDX permissions to the Project as a whole, "
73 "nor to the RIFT.ware platform in general.") },
75 { 'mano-role':"rw-project-mano:account-oper",
76 'description':("The account-oper Role has read permission to the VIM, SDN, VCA "
77 "and RO accounts within a Project. The account-oper Role may also have "
78 "execute permission to specific non-mutating RPCs.") },
82 class ProjectDtsHandler(object):
83 XPATH
= "C,/{}".format(NS_PROJECT
)
85 def __init__(self
, dts
, log
, callbacks
):
88 self
._callbacks
= callbacks
101 def get_reg_flags(self
):
102 return (rwdts
.Flag
.SUBSCRIBER |
103 rwdts
.Flag
.DELTA_READY |
105 rwdts
.Flag
.DATASTORE
)
107 def add_project(self
, cfg
):
109 self
._log
.info("Adding project: {}".format(name
))
111 if name
not in self
.projects
:
112 self
._callbacks
.on_add_apply(name
, cfg
)
113 self
.projects
.append(name
)
115 self
._log
.error("Project already present: {}".
118 def delete_project(self
, name
):
119 self
._log
.info("Deleting project: {}".format(name
))
120 if name
in self
.projects
:
121 self
._callbacks
.on_delete_apply(name
)
122 self
.projects
.remove(name
)
124 self
._log
.error("Unrecognized project: {}".
127 def update_project(self
, cfg
):
128 """ Update an existing project
130 Currently, we do not take any action on MANO for this,
131 so no callbacks are defined
134 msg - The project config message
137 self
._log
.info("Updating project: {}".format(name
))
138 if name
in self
.projects
:
141 self
._log
.error("Unrecognized project: {}".
146 def apply_config(dts
, acg
, xact
, action
, scratch
):
147 self
._log
.debug("Got project apply config (xact: %s) (action: %s)", xact
, action
)
149 if xact
.xact
is None:
150 if action
== rwdts
.AppconfAction
.INSTALL
:
151 curr_cfg
= self
._reg
.elements
153 self
._log
.info("Project {} being re-added after restart.".
155 self
.add_project(cfg
)
157 self
._log
.debug("No xact handle. Skipping apply config")
161 add_cfgs
, delete_cfgs
, update_cfgs
= get_add_delete_update_cfgs(
162 dts_member_reg
=self
._reg
,
168 for cfg
in delete_cfgs
:
169 self
.delete_project(cfg
.name
)
173 self
.add_project(cfg
)
176 for cfg
in update_cfgs
:
177 self
.update_project(cfg
)
179 return RwTypes
.RwStatus
.SUCCESS
182 def on_prepare(dts
, acg
, xact
, xact_info
, ks_path
, msg
, scratch
):
183 """ Prepare callback from DTS for Project """
185 action
= xact_info
.query_action
188 self
._log
.debug("Project %s on_prepare config received (action: %s): %s",
189 name
, xact_info
.query_action
, msg
)
191 if action
in [rwdts
.QueryAction
.CREATE
, rwdts
.QueryAction
.UPDATE
]:
192 if name
in self
.projects
:
193 self
._log
.debug("Project {} already exists. Ignore request".
197 self
._log
.debug("Project {}: Invoking on_prepare add request".
199 rc
, err_msg
= yield from self
._callbacks
.on_add_prepare(name
, msg
)
201 xact_info
.send_error_xpath(RwTypes
.RwStatus
.FAILURE
,
202 ProjectDtsHandler
.XPATH
,
204 xact_info
.respond_xpath(rwdts
.XactRspCode
.NACK
)
207 elif action
== rwdts
.QueryAction
.DELETE
:
208 # Check if the entire project got deleted
209 fref
= ProtobufC
.FieldReference
.alloc()
210 fref
.goto_whole_message(msg
.to_pbcm())
211 if fref
.is_field_deleted():
212 if name
in self
.projects
:
213 rc
, delete_msg
= yield from self
._callbacks
.on_delete_prepare(name
)
215 self
._log
.error("Project {} should not be deleted. Reason : {}".
216 format(name
, delete_msg
))
218 xact_info
.send_error_xpath(RwTypes
.RwStatus
.FAILURE
,
219 ProjectDtsHandler
.XPATH
,
222 xact_info
.respond_xpath(rwdts
.XactRspCode
.NACK
)
225 self
._log
.warning("Delete on unknown project: {}".
229 self
._log
.error("Action (%s) NOT SUPPORTED", action
)
230 xact_info
.respond_xpath(rwdts
.XactRspCode
.NA
)
233 xact_info
.respond_xpath(rwdts
.XactRspCode
.ACK
)
235 self
._log
.debug("Registering for project config using xpath: %s",
236 ProjectDtsHandler
.XPATH
)
238 acg_handler
= rift
.tasklets
.AppConfGroup
.Handler(
239 on_apply
=apply_config
,
242 with self
._dts
.appconf_group_create(acg_handler
) as acg
:
243 self
._reg
= acg
.register(
244 xpath
=ProjectDtsHandler
.XPATH
,
245 flags
=self
.get_reg_flags(),
246 on_prepare
=on_prepare
,
250 class ProjectHandler(object):
251 def __init__(self
, tasklet
, project_class
):
252 self
._tasklet
= tasklet
253 self
._log
= tasklet
.log
254 self
._log
_hdl
= tasklet
.log_hdl
255 self
._dts
= tasklet
.dts
256 self
._loop
= tasklet
.loop
257 self
._class
= project_class
259 self
.mano_roles
= [role
['mano-role'] for role
in MANO_PROJECT_ROLES
]
261 self
._log
.debug("Creating project config handler")
262 self
.project_cfg_handler
= ProjectDtsHandler(
263 self
._dts
, self
._log
,
264 ProjectConfigCallbacks(
265 on_add_apply
=self
.on_project_added
,
266 on_add_prepare
=self
.on_add_prepare
,
267 on_delete_apply
=self
.on_project_deleted
,
268 on_delete_prepare
=self
.on_delete_prepare
,
272 def _get_tasklet_name(self
):
273 return self
._tasklet
.tasklet_info
.instance_name
275 def _get_project(self
, name
):
277 proj
= self
._tasklet
.projects
[name
]
278 except Exception as e
:
279 self
._log
.exception("Project {} ({})not found for tasklet {}: {}".
280 format(name
, list(self
._tasklet
.projects
.keys()),
281 self
._get
_tasklet
_name
(), e
))
286 def on_project_deleted(self
, name
):
287 self
._log
.debug("Project {} deleted".format(name
))
289 self
._get
_project
(name
).deregister()
290 except Exception as e
:
291 self
._log
.exception("Project {} deregister for {} failed: {}".
292 format(name
, self
._get
_tasklet
_name
(), e
))
295 proj
= self
._tasklet
.projects
.pop(name
)
297 except Exception as e
:
298 self
._log
.exception("Project {} delete for {} failed: {}".
299 format(name
, self
._get
_tasklet
_name
(), e
))
301 def on_project_added(self
, name
, cfg
):
302 if name
not in self
._tasklet
.projects
:
304 self
._tasklet
.projects
[name
] = \
305 self
._class
(name
, self
._tasklet
)
306 self
._loop
.create_task(self
._get
_project
(name
).register())
308 except Exception as e
:
309 self
._log
.exception("Project {} create for {} failed: {}".
310 format(name
, self
._get
_tasklet
_name
(), e
))
313 self
._log
.debug("Project {} added to tasklet {}".
314 format(name
, self
._get
_tasklet
_name
()))
315 self
._get
_project
(name
)._apply
= True
318 def on_add_prepare(self
, name
, msg
):
319 self
._log
.debug("Project {} to be added to {}".
320 format(name
, self
._get
_tasklet
_name
()))
322 if name
in self
._tasklet
.projects
:
323 err_msg
= ("Project already exists: {}".
325 self
._log
.error(err_msg
)
326 return False, err_msg
328 # Validate mano-roles, if present
330 cfg
= msg
.project_config
333 for role
in user
.mano_role
:
334 if role
.role
not in self
.mano_roles
:
335 err_msg
= ("Invalid role {} for user {} in project {}".
336 format(role
.role
, user
.user_name
, name
))
337 self
._log
.error(err_msg
)
338 return False, err_msg
340 except AttributeError as e
:
341 # If the user or mano role is not present, ignore
342 self
._log
.debug("Project {}: {}".format(name
, e
))
347 def on_delete_prepare(self
, name
):
348 self
._log
.error("Project {} being deleted for tasklet {}".
349 format(name
, self
._get
_tasklet
_name
()))
350 rc
, delete_msg
= yield from self
._get
_project
(name
).delete_prepare()
351 return (rc
, delete_msg
)
354 self
.project_cfg_handler
.register()
357 class ProjectStateRolePublisher(rift
.tasklets
.DtsConfigPublisher
):
359 def __init__(self
, tasklet
):
360 super().__init
__(tasklet
)
361 self
.proj_state
= RwProjectManoYang
.YangData_RwProject_Project_ProjectState()
362 self
.projects
= set()
363 self
.roles
= MANO_PROJECT_ROLES
366 return "D,/rw-project:project/rw-project:project-state/rw-project-mano:mano-role"
368 def get_reg_flags(self
):
369 return super().get_reg_flags() | rwdts
.Flag
.DATASTORE
371 def role_xpath(self
, project
, role
):
372 return "/rw-project:project[rw-project:name={}]".format(quoted_key(project
)) + \
373 "/rw-project:project-state/rw-project-mano:mano-role" + \
374 "[rw-project-mano:role={}]".format(quoted_key(role
['mano-role']))
376 def pb_role(self
, role
):
377 pbRole
= self
.proj_state
.create_mano_role()
378 pbRole
.role
= role
['mano-role']
379 pbRole
.description
= role
['description']
382 def publish_roles(self
, project
):
383 if not project
in self
.projects
:
384 self
.projects
.add(project
)
385 for role
in self
.roles
:
386 xpath
= self
.role_xpath(project
, role
)
387 pb_role
= self
.pb_role(role
)
388 self
.log
.debug("publishing xpath:{}".format(xpath
))
389 self
._regh
.update_element(xpath
, pb_role
)
391 def unpublish_roles(self
, project
):
392 if project
in self
.projects
:
393 self
.projects
.remove(project
)
394 for role
in self
.roles
:
395 xpath
= self
.role_xpath(project
, role
)
396 self
.log
.debug("unpublishing xpath:{}".format(xpath
))
397 self
._regh
.delete_element(xpath
)