Add MANO project role support
[osm/SO.git] / rwprojectmano / plugins / rwprojectmano / rift / tasklets / rwprojectmano / projectmano.py
1 #
2 # Copyright 2017 RIFT.IO Inc
3 #
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
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 #
16
17 """
18 Project Manager tasklet is responsible for managing the Projects
19 configurations required for Role Based Access Control feature.
20 """
21
22
23 import asyncio
24
25 import gi
26 gi.require_version('RwDts', '1.0')
27 from gi.repository import (
28 RwDts as rwdts,
29 ProtobufC,
30 RwTypes,
31 )
32
33 import rift.tasklets
34 from rift.mano.utils.project import (
35 NS_PROJECT,
36 get_add_delete_update_cfgs,
37 ProjectConfigCallbacks,
38 )
39
40
41 class ProjectDtsHandler(object):
42 XPATH = "C,/{}".format(NS_PROJECT)
43
44 def __init__(self, dts, log, callbacks):
45 self._dts = dts
46 self._log = log
47 self._callbacks = callbacks
48
49 self.reg = None
50 self.projects = []
51
52 @property
53 def log(self):
54 return self._log
55
56 @property
57 def dts(self):
58 return self._dts
59
60 def add_project(self, cfg):
61 name = cfg.name
62 self.log.info("Adding project: {}".format(name))
63
64 if name not in self.projects:
65 self._callbacks.on_add_apply(name, cfg)
66 self.projects.append(name)
67 else:
68 self.log.error("Project already present: {}".
69 format(name))
70
71 def delete_project(self, name):
72 self._log.info("Deleting project: {}".format(name))
73 if name in self.projects:
74 self._callbacks.on_delete_apply(name)
75 self.projects.remove(name)
76 else:
77 self.log.error("Unrecognized project: {}".
78 format(name))
79
80 def update_project(self, cfg):
81 """ Update an existing project
82
83 Currently, we do not take any action on MANO for this,
84 so no callbacks are defined
85
86 Arguments:
87 msg - The project config message
88 """
89 name = cfg.name
90 self._log.info("Updating project: {}".format(name))
91 if name in self.projects:
92 pass
93 else:
94 self.log.error("Unrecognized project: {}".
95 format(name))
96
97 def register(self):
98 @asyncio.coroutine
99 def apply_config(dts, acg, xact, action, scratch):
100 self._log.debug("Got project apply config (xact: %s) (action: %s)", xact, action)
101
102 if xact.xact is None:
103 if action == rwdts.AppconfAction.INSTALL:
104 curr_cfg = self._reg.elements
105 for cfg in curr_cfg:
106 self._log.debug("Project being re-added after restart.")
107 self.add_project(cfg.name)
108 else:
109 # When RIFT first comes up, an INSTALL is called with the current config
110 # Since confd doesn't actally persist data this never has any data so
111 # skip this for now.
112 self._log.debug("No xact handle. Skipping apply config")
113
114 return
115
116 add_cfgs, delete_cfgs, update_cfgs = get_add_delete_update_cfgs(
117 dts_member_reg=self._reg,
118 xact=xact,
119 key_name="name",
120 )
121
122 # Handle Deletes
123 for cfg in delete_cfgs:
124 self.delete_project(cfg.name)
125
126 # Handle Adds
127 for cfg in add_cfgs:
128 self.add_project(cfg)
129
130 # Handle Updates
131 for cfg in update_cfgs:
132 self.update_project(cfg)
133
134 return RwTypes.RwStatus.SUCCESS
135
136 @asyncio.coroutine
137 def on_prepare(dts, acg, xact, xact_info, ks_path, msg, scratch):
138 """ Prepare callback from DTS for Project """
139
140 action = xact_info.query_action
141 name = msg.name
142
143 self._log.debug("Project %s on_prepare config received (action: %s): %s",
144 name, xact_info.query_action, msg)
145
146 if action in [rwdts.QueryAction.CREATE, rwdts.QueryAction.UPDATE]:
147 if name in self.projects:
148 self._log.debug("Project {} already exists. Ignore request".
149 format(name))
150
151 else:
152 self._log.debug("Project {}: Invoking on_prepare add request".
153 format(name))
154 yield from self._callbacks.on_add_prepare(name, msg)
155
156
157 elif action == rwdts.QueryAction.DELETE:
158 # Check if the entire project got deleted
159 fref = ProtobufC.FieldReference.alloc()
160 fref.goto_whole_message(msg.to_pbcm())
161 if fref.is_field_deleted():
162 if name in self.projects:
163 rc = yield from self._callbacks.on_delete_prepare(name)
164 if not rc:
165 self._log.error("Project {} should not be deleted".
166 format(name))
167 xact_info.respond_xpath(rwdts.XactRspCode.NACK)
168 return
169 else:
170 self._log.warning("Delete on unknown project: {}".
171 format(name))
172
173 else:
174 self._log.error("Action (%s) NOT SUPPORTED", action)
175 xact_info.respond_xpath(rwdts.XactRspCode.NACK)
176 return
177
178 xact_info.respond_xpath(rwdts.XactRspCode.ACK)
179
180 self._log.debug("Registering for project config using xpath: %s",
181 ProjectDtsHandler.XPATH)
182
183 acg_handler = rift.tasklets.AppConfGroup.Handler(
184 on_apply=apply_config,
185 )
186
187 with self._dts.appconf_group_create(acg_handler) as acg:
188 self._reg = acg.register(
189 xpath=ProjectDtsHandler.XPATH,
190 flags=rwdts.Flag.SUBSCRIBER | rwdts.Flag.DELTA_READY | rwdts.Flag.CACHE,
191 on_prepare=on_prepare,
192 )
193
194
195 class ProjectHandler(object):
196 def __init__(self, tasklet, project_class):
197 self._tasklet = tasklet
198 self._log = tasklet.log
199 self._log_hdl = tasklet.log_hdl
200 self._dts = tasklet.dts
201 self._loop = tasklet.loop
202 self._class = project_class
203
204 self._log.debug("Creating project config handler")
205 self.project_cfg_handler = ProjectDtsHandler(
206 self._dts, self._log,
207 ProjectConfigCallbacks(
208 on_add_apply=self.on_project_added,
209 on_add_prepare=self.on_add_prepare,
210 on_delete_apply=self.on_project_deleted,
211 on_delete_prepare=self.on_delete_prepare,
212 )
213 )
214
215 def _get_tasklet_name(self):
216 return self._tasklet.tasklet_info.instance_name
217
218 def _get_project(self, name):
219 try:
220 proj = self._tasklet.projects[name]
221 except Exception as e:
222 self._log.exception("Project {} ({})not found for tasklet {}: {}".
223 format(name, list(self._tasklet.projects.keys()),
224 self._get_tasklet_name(), e))
225 raise e
226
227 return proj
228
229 def on_project_deleted(self, name):
230 self._log.debug("Project {} deleted".format(name))
231 try:
232 self._get_project(name).deregister()
233 except Exception as e:
234 self._log.exception("Project {} deregister for {} failed: {}".
235 format(name, self._get_tasklet_name(), e))
236
237 try:
238 proj = self._tasklet.projects.pop(name)
239 del proj
240 except Exception as e:
241 self._log.exception("Project {} delete for {} failed: {}".
242 format(name, self._get_tasklet_name(), e))
243
244 def on_project_added(self, name, cfg):
245 self._log.debug("Project {} added to tasklet {}".
246 format(name, self._get_tasklet_name()))
247 self._get_project(name)._apply = True
248
249 @asyncio.coroutine
250 def on_add_prepare(self, name, msg):
251 self._log.debug("Project {} to be added to {}".
252 format(name, self._get_tasklet_name()))
253
254 try:
255 self._tasklet.projects[name] = \
256 self._class(name, self._tasklet)
257 except Exception as e:
258 self._log.exception("Project {} create for {} failed: {}".
259 format(name, self._get_tasklet_name(), e))
260
261 try:
262 yield from self._get_project(name).register()
263 except Exception as e:
264 self._log.exception("Project {} register for tasklet {} failed: {}".
265 format(name, self._get_tasklet_name(), e))
266
267 self._log.debug("Project {} added to {}".
268 format(name, self._get_tasklet_name()))
269
270 @asyncio.coroutine
271 def on_delete_prepare(self, name):
272 self._log.debug("Project {} being deleted for tasklet {}".
273 format(name, self._get_tasklet_name()))
274 rc = yield from self._get_project(name).delete_prepare()
275 return rc
276
277 def register(self):
278 self.project_cfg_handler.register()