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.
26 gi
.require_version('RwDts', '1.0')
27 gi
.require_version('RwProjectManoYang', '1.0')
28 from gi
.repository
import (
36 from rift
.mano
.utils
.project
import (
38 get_add_delete_update_cfgs
,
39 ProjectConfigCallbacks
,
43 MANO_PROJECT_ROLES
= [
44 { 'mano-role':"rw-project-mano:catalog-oper",
45 'description':("The catalog-oper Role has read permission to nsd-catalog "
46 "and vnfd-catalog under specific Projects, "
47 "as identified by /rw-project:project/rw-project:name. The "
48 "catatlog-oper Role may also have execute permission to specific "
49 "non-mutating RPCs. This Role is intended for read-only access to "
50 "catalogs under a specific project.") },
52 { 'mano-role':"rw-project-mano:catalog-admin",
53 'description':("The catalog-admin Role has full CRUDX permissions to vnfd and nsd "
54 "catalogs under specific Projects, as identified by "
55 "/rw-project:project/rw-project:name.") },
57 { 'mano-role':"rw-project-mano:lcm-oper",
58 'description':("The lcm-oper Role has read permission to the VL, VNF and NS "
59 "records within a Project. The lcm-oper Role may also have "
60 "execute permission to specific non-mutating RPCs.") },
62 { 'mano-role':"rw-project-mano:lcm-admin",
63 'description':("The lcm-admin Role has full CRUDX permissions to the VL, VNF "
64 "and NS records within a Project. The lcm-admin Role does "
65 "not provide general CRUDX permissions to the Project as a whole, "
66 "nor to the RIFT.ware platform in general.") },
68 { 'mano-role':"rw-project-mano:account-oper",
69 'description':("The account-oper Role has read permission to the VIM, SDN, VCA "
70 "and RO accounts within a Project. The account-oper Role may also have "
71 "execute permission to specific non-mutating RPCs.") },
73 { 'mano-role':"rw-project-mano:account-admin",
74 'description':("The account-admin Role has full CRUDX permissions to the VIM, SDN, VCA "
75 "and RO accounts within a Project. The account-admin Role does "
76 "not provide general CRUDX permissions to the Project as a whole, "
77 "nor to the RIFT.ware platform in general.") },
81 class ProjectDtsHandler(object):
82 XPATH
= "C,/{}".format(NS_PROJECT
)
84 def __init__(self
, dts
, log
, callbacks
):
87 self
._callbacks
= callbacks
100 def add_project(self
, cfg
):
102 self
.log
.info("Adding project: {}".format(name
))
104 if name
not in self
.projects
:
105 self
._callbacks
.on_add_apply(name
, cfg
)
106 self
.projects
.append(name
)
108 self
.log
.error("Project already present: {}".
111 def delete_project(self
, name
):
112 self
._log
.info("Deleting project: {}".format(name
))
113 if name
in self
.projects
:
114 self
._callbacks
.on_delete_apply(name
)
115 self
.projects
.remove(name
)
117 self
.log
.error("Unrecognized project: {}".
120 def update_project(self
, cfg
):
121 """ Update an existing project
123 Currently, we do not take any action on MANO for this,
124 so no callbacks are defined
127 msg - The project config message
130 self
._log
.info("Updating project: {}".format(name
))
131 if name
in self
.projects
:
134 self
.log
.error("Unrecognized project: {}".
139 def apply_config(dts
, acg
, xact
, action
, scratch
):
140 self
._log
.debug("Got project apply config (xact: %s) (action: %s)", xact
, action
)
142 if xact
.xact
is None:
143 if action
== rwdts
.AppconfAction
.INSTALL
:
144 curr_cfg
= self
._reg
.elements
146 self
._log
.debug("Project being re-added after restart.")
147 self
.add_project(cfg
.name
)
149 # When RIFT first comes up, an INSTALL is called with the current config
150 # Since confd doesn't actally persist data this never has any data so
152 self
._log
.debug("No xact handle. Skipping apply config")
156 add_cfgs
, delete_cfgs
, update_cfgs
= get_add_delete_update_cfgs(
157 dts_member_reg
=self
._reg
,
163 for cfg
in delete_cfgs
:
164 self
.delete_project(cfg
.name
)
168 self
.add_project(cfg
)
171 for cfg
in update_cfgs
:
172 self
.update_project(cfg
)
174 return RwTypes
.RwStatus
.SUCCESS
177 def on_prepare(dts
, acg
, xact
, xact_info
, ks_path
, msg
, scratch
):
178 """ Prepare callback from DTS for Project """
180 action
= xact_info
.query_action
183 self
._log
.debug("Project %s on_prepare config received (action: %s): %s",
184 name
, xact_info
.query_action
, msg
)
186 if action
in [rwdts
.QueryAction
.CREATE
, rwdts
.QueryAction
.UPDATE
]:
187 if name
in self
.projects
:
188 self
._log
.debug("Project {} already exists. Ignore request".
192 self
._log
.debug("Project {}: Invoking on_prepare add request".
194 yield from self
._callbacks
.on_add_prepare(name
, msg
)
197 elif action
== rwdts
.QueryAction
.DELETE
:
198 # Check if the entire project got deleted
199 fref
= ProtobufC
.FieldReference
.alloc()
200 fref
.goto_whole_message(msg
.to_pbcm())
201 if fref
.is_field_deleted():
202 if name
in self
.projects
:
203 rc
= yield from self
._callbacks
.on_delete_prepare(name
)
205 self
._log
.error("Project {} should not be deleted".
207 xact_info
.respond_xpath(rwdts
.XactRspCode
.NACK
)
210 self
._log
.warning("Delete on unknown project: {}".
214 self
._log
.error("Action (%s) NOT SUPPORTED", action
)
215 xact_info
.respond_xpath(rwdts
.XactRspCode
.NACK
)
218 xact_info
.respond_xpath(rwdts
.XactRspCode
.ACK
)
220 self
._log
.debug("Registering for project config using xpath: %s",
221 ProjectDtsHandler
.XPATH
)
223 acg_handler
= rift
.tasklets
.AppConfGroup
.Handler(
224 on_apply
=apply_config
,
227 with self
._dts
.appconf_group_create(acg_handler
) as acg
:
228 self
._reg
= acg
.register(
229 xpath
=ProjectDtsHandler
.XPATH
,
230 flags
=rwdts
.Flag
.SUBSCRIBER | rwdts
.Flag
.DELTA_READY | rwdts
.Flag
.CACHE
,
231 on_prepare
=on_prepare
,
235 class ProjectHandler(object):
236 def __init__(self
, tasklet
, project_class
):
237 self
._tasklet
= tasklet
238 self
._log
= tasklet
.log
239 self
._log
_hdl
= tasklet
.log_hdl
240 self
._dts
= tasklet
.dts
241 self
._loop
= tasklet
.loop
242 self
._class
= project_class
244 self
._log
.debug("Creating project config handler")
245 self
.project_cfg_handler
= ProjectDtsHandler(
246 self
._dts
, self
._log
,
247 ProjectConfigCallbacks(
248 on_add_apply
=self
.on_project_added
,
249 on_add_prepare
=self
.on_add_prepare
,
250 on_delete_apply
=self
.on_project_deleted
,
251 on_delete_prepare
=self
.on_delete_prepare
,
255 def _get_tasklet_name(self
):
256 return self
._tasklet
.tasklet_info
.instance_name
258 def _get_project(self
, name
):
260 proj
= self
._tasklet
.projects
[name
]
261 except Exception as e
:
262 self
._log
.exception("Project {} ({})not found for tasklet {}: {}".
263 format(name
, list(self
._tasklet
.projects
.keys()),
264 self
._get
_tasklet
_name
(), e
))
269 def on_project_deleted(self
, name
):
270 self
._log
.debug("Project {} deleted".format(name
))
272 self
._get
_project
(name
).deregister()
273 except Exception as e
:
274 self
._log
.exception("Project {} deregister for {} failed: {}".
275 format(name
, self
._get
_tasklet
_name
(), e
))
278 proj
= self
._tasklet
.projects
.pop(name
)
280 except Exception as e
:
281 self
._log
.exception("Project {} delete for {} failed: {}".
282 format(name
, self
._get
_tasklet
_name
(), e
))
284 def on_project_added(self
, name
, cfg
):
285 self
._log
.debug("Project {} added to tasklet {}".
286 format(name
, self
._get
_tasklet
_name
()))
287 self
._get
_project
(name
)._apply
= True
290 def on_add_prepare(self
, name
, msg
):
291 self
._log
.debug("Project {} to be added to {}".
292 format(name
, self
._get
_tasklet
_name
()))
295 self
._tasklet
.projects
[name
] = \
296 self
._class
(name
, self
._tasklet
)
297 except Exception as e
:
298 self
._log
.exception("Project {} create for {} failed: {}".
299 format(name
, self
._get
_tasklet
_name
(), e
))
302 yield from self
._get
_project
(name
).register()
303 except Exception as e
:
304 self
._log
.exception("Project {} register for tasklet {} failed: {}".
305 format(name
, self
._get
_tasklet
_name
(), e
))
307 self
._log
.debug("Project {} added to {}".
308 format(name
, self
._get
_tasklet
_name
()))
311 def on_delete_prepare(self
, name
):
312 self
._log
.debug("Project {} being deleted for tasklet {}".
313 format(name
, self
._get
_tasklet
_name
()))
314 rc
= yield from self
._get
_project
(name
).delete_prepare()
318 self
.project_cfg_handler
.register()
321 class ProjectStateRolePublisher(rift
.tasklets
.DtsConfigPublisher
):
323 def __init__(self
, tasklet
):
324 super().__init
__(tasklet
)
325 self
.proj_state
= RwProjectManoYang
.YangData_RwProject_Project_ProjectState()
326 self
.projects
= set()
327 self
.roles
= MANO_PROJECT_ROLES
330 return "D,/rw-project:project/rw-project:project-state/rw-project-mano:mano-role"
332 def role_xpath(self
, project
, role
):
333 return "/rw-project:project[rw-project:name='{}']".format(project
) + \
334 "/rw-project:project-state/rw-project-mano:mano-role" + \
335 "[rw-project-mano:role='{}']".format(role
['mano-role'])
337 def pb_role(self
, role
):
338 pbRole
= self
.proj_state
.create_mano_role()
339 pbRole
.role
= role
['mano-role']
340 pbRole
.description
= role
['description']
343 def publish_roles(self
, project
):
344 if not project
in self
.projects
:
345 self
.projects
.add(project
)
346 for role
in self
.roles
:
347 xpath
= self
.role_xpath(project
, role
)
348 pb_role
= self
.pb_role(role
)
349 self
.log
.debug("publishing xpath:{}".format(xpath
))
350 self
._regh
.update_element(xpath
, pb_role
)
352 def unpublish_roles(self
, project
):
353 if project
in self
.projects
:
354 self
.projects
.remove(project
)
355 for role
in self
.roles
:
356 xpath
= self
.role_xpath(project
, role
)
357 self
.log
.debug("unpublishing xpath:{}".format(xpath
))
358 self
._regh
.delete_element(xpath
)