Merge from OSM SO master
[osm/SO.git] / rwcm / plugins / rwconman / rift / tasklets / rwconmantasklet / rwconman_events.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18
19 import ncclient
20 import ncclient.asyncio_manager
21 import tornado.httpclient as tornadoh
22 import asyncio.subprocess
23 import asyncio
24 import time
25 import sys
26 import os, stat
27
28 import gi
29 gi.require_version('RwDts', '1.0')
30 gi.require_version('RwYang', '1.0')
31 gi.require_version('RwConmanYang', '1.0')
32 gi.require_version('RwNsrYang', '1.0')
33 gi.require_version('RwVnfrYang', '1.0')
34
35 from gi.repository import (
36 RwDts as rwdts,
37 RwYang,
38 RwConmanYang as conmanY,
39 RwNsrYang as nsrY,
40 RwVnfrYang as vnfrY,
41 )
42
43 import rift.tasklets
44
45 if sys.version_info < (3, 4, 4):
46 asyncio.ensure_future = asyncio.async
47
48 def log_this_vnf(vnf_cfg):
49 log_vnf = ""
50 used_item_list = ['nsr_name', 'vnfr_name', 'member_vnf_index', 'mgmt_ip_address']
51 for item in used_item_list:
52 if item in vnf_cfg:
53 if item == 'mgmt_ip_address':
54 log_vnf += "({})".format(vnf_cfg[item])
55 else:
56 log_vnf += "{}/".format(vnf_cfg[item])
57 return log_vnf
58
59
60 class ConfigManagerROifConnectionError(Exception):
61 pass
62
63
64 class ScriptError(Exception):
65 pass
66
67
68 class ConfigManagerEvents(object):
69 def __init__(self, dts, log, loop, parent):
70 self._dts = dts
71 self._log = log
72 self._loop = loop
73 self._parent = parent
74 self._nsr_xpath = parent._project.add_project("/cm-state/cm-nsr")
75
76 @asyncio.coroutine
77 def register(self):
78 pass
79
80 def deregister(self):
81 pass
82
83 @asyncio.coroutine
84 def update_vnf_state(self, vnf_cfg, state):
85 nsr_obj = vnf_cfg['nsr_obj']
86 yield from nsr_obj.update_vnf_cm_state(vnf_cfg['vnfr'], state)
87
88 @asyncio.coroutine
89 def apply_vnf_config(self, vnf_cfg):
90 self._log.debug("apply_vnf_config VNF:{}"
91 .format(log_this_vnf(vnf_cfg)))
92
93 if vnf_cfg['config_delay']:
94 yield from self.update_vnf_state(vnf_cfg, conmanY.RecordState.CFG_DELAY)
95 yield from asyncio.sleep(vnf_cfg['config_delay'], loop=self._loop)
96
97 # See if we are still alive!
98 if vnf_cfg['nsr_obj'].being_deleted:
99 # Don't do anything, just return
100 self._log.info("VNF : %s is being deleted, skipping configuration!",
101 log_this_vnf(vnf_cfg))
102 return True
103
104 yield from self.update_vnf_state(vnf_cfg, conmanY.RecordState.CFG_SEND)
105 try:
106 if vnf_cfg['config_method'] == 'netconf':
107 self._log.info("Creating ncc handle for VNF cfg = %s!", vnf_cfg)
108 self.ncc = ConfigManagerVNFnetconf(self._log, self._loop, self, vnf_cfg)
109 if vnf_cfg['protocol'] == 'ssh':
110 yield from self.ncc.connect_ssh()
111 else:
112 yield from self.ncc.connect()
113 yield from self.ncc.apply_edit_cfg()
114 elif vnf_cfg['config_method'] == 'rest':
115 if self.rcc is None:
116 self._log.info("Creating rcc handle for VNF cfg = %s!", vnf_cfg)
117 self.rcc = ConfigManagerVNFrestconf(self._log, self._loop, self, vnf_cfg)
118 self.ncc.apply_edit_cfg()
119 elif vnf_cfg['config_method'] == 'script':
120 self._log.info("Executing script for VNF cfg = %s!", vnf_cfg)
121 scriptc = ConfigManagerVNFscriptconf(self._log, self._loop, self, vnf_cfg)
122 yield from scriptc.apply_edit_cfg()
123 elif vnf_cfg['config_method'] == 'juju':
124 self._log.info("Executing juju config for VNF cfg = %s!", vnf_cfg)
125 jujuc = ConfigManagerVNFjujuconf(self._log, self._loop, self._parent, vnf_cfg)
126 yield from jujuc.apply_edit_cfg()
127 else:
128 self._log.error("Unknown configuration method(%s) received for %s",
129 vnf_cfg['config_method'], vnf_cfg['vnf_unique_name'])
130 yield from self.update_vnf_state(vnf_cfg, conmanY.RecordState.CFG_FAILED)
131 return True
132
133 #Update VNF state
134 yield from self.update_vnf_state(vnf_cfg, conmanY.RecordState.READY)
135 self._log.info("Successfully applied configuration to VNF: %s",
136 log_this_vnf(vnf_cfg))
137 except Exception as e:
138 self._log.error("Applying configuration(%s) file(%s) to VNF: %s failed as: %s",
139 vnf_cfg['config_method'],
140 vnf_cfg['cfg_file'],
141 log_this_vnf(vnf_cfg),
142 str(e))
143 #raise
144 return False
145
146 return True
147
148 class ConfigManagerVNFscriptconf(object):
149
150 def __init__(self, log, loop, parent, vnf_cfg):
151 self._log = log
152 self._loop = loop
153 self._parent = parent
154 self._manager = None
155 self._vnf_cfg = vnf_cfg
156
157 #@asyncio.coroutine
158 def apply_edit_cfg(self):
159 vnf_cfg = self._vnf_cfg
160 self._log.debug("Attempting to apply scriptconf to VNF: %s", log_this_vnf(vnf_cfg))
161 try:
162 st = os.stat(vnf_cfg['cfg_file'])
163 os.chmod(vnf_cfg['cfg_file'], st.st_mode | stat.S_IEXEC)
164 #script_msg = subprocess.check_output(vnf_cfg['cfg_file'], shell=True).decode('utf-8')
165
166 proc = yield from asyncio.create_subprocess_exec(
167 vnf_cfg['script_type'], vnf_cfg['cfg_file'],
168 stdout=asyncio.subprocess.PIPE)
169 script_msg = yield from proc.stdout.read()
170 rc = yield from proc.wait()
171
172 if rc != 0:
173 raise ScriptError(
174 "script config returned error code : %s" % rc
175 )
176
177 self._log.debug("config script output (%s)", script_msg)
178 except Exception as e:
179 self._log.error("Error (%s) while executing script config for VNF: %s",
180 str(e), log_this_vnf(vnf_cfg))
181 raise
182
183 class ConfigManagerVNFrestconf(object):
184
185 def __init__(self, log, loop, parent, vnf_cfg):
186 self._log = log
187 self._loop = loop
188 self._parent = parent
189 self._manager = None
190 self._vnf_cfg = vnf_cfg
191
192 def fetch_handle(self, response):
193 if response.error:
194 self._log.error("Failed to send HTTP config request - %s", response.error)
195 else:
196 self._log.debug("Sent HTTP config request - %s", response.body)
197
198 @asyncio.coroutine
199 def apply_edit_cfg(self):
200 vnf_cfg = self._vnf_cfg
201 self._log.debug("Attempting to apply restconf to VNF: %s", log_this_vnf(vnf_cfg))
202 try:
203 http_c = tornadoh.AsyncHTTPClient()
204 # TBD
205 # Read the config entity from file?
206 # Convert connectoin-point?
207 http_c.fetch("http://", self.fetch_handle)
208 except Exception as e:
209 self._log.error("Error (%s) while applying HTTP config", str(e))
210
211 class ConfigManagerVNFnetconf(object):
212
213 def __init__(self, log, loop, parent, vnf_cfg):
214 self._log = log
215 self._loop = loop
216 self._parent = parent
217 self._manager = None
218 self._vnf_cfg = vnf_cfg
219
220 self._model = RwYang.Model.create_libncx()
221 self._model.load_schema_ypbc(conmanY.get_schema())
222
223 @asyncio.coroutine
224 def connect(self, timeout_secs=120):
225 vnf_cfg = self._vnf_cfg
226 start_time = time.time()
227 self._log.debug("connecting netconf .... %s", vnf_cfg)
228 while (time.time() - start_time) < timeout_secs:
229
230 try:
231 self._log.info("Attemping netconf connection to VNF: %s", log_this_vnf(vnf_cfg))
232
233 self._manager = yield from ncclient.asyncio_manager.asyncio_connect(
234 loop=self._loop,
235 host=vnf_cfg['mgmt_ip_address'],
236 port=vnf_cfg['port'],
237 username=vnf_cfg['username'],
238 password=vnf_cfg['password'],
239 allow_agent=False,
240 look_for_keys=False,
241 hostkey_verify=False,
242 )
243
244 self._log.info("Netconf connected to VNF: %s", log_this_vnf(vnf_cfg))
245 return
246
247 except ncclient.transport.errors.SSHError as e:
248 yield from self._parent.update_vnf_state(vnf_cfg, conmanY.RecordState.FAILED_CONNECTION)
249 self._log.error("Netconf connection to VNF: %s, failed: %s",
250 log_this_vnf(vnf_cfg), str(e))
251
252 yield from asyncio.sleep(2, loop=self._loop)
253
254 raise ConfigManagerROifConnectionError(
255 "Failed to connect to VNF: %s within %s seconds" %
256 (log_this_vnf(vnf_cfg), timeout_secs)
257 )
258
259 @asyncio.coroutine
260 def connect_ssh(self, timeout_secs=120):
261 vnf_cfg = self._vnf_cfg
262 start_time = time.time()
263
264 if (self._manager != None and self._manager.connected == True):
265 self._log.debug("Disconnecting previous session")
266 self._manager.close_session
267
268 self._log.debug("connecting netconf via SSH .... %s", vnf_cfg)
269 while (time.time() - start_time) < timeout_secs:
270
271 try:
272 yield from self._parent.update_vnf_state(vnf_cfg, conmanY.RecordState.CONNECTING)
273 self._log.debug("Attemping netconf connection to VNF: %s", log_this_vnf(vnf_cfg))
274
275 self._manager = ncclient.asyncio_manager.manager.connect_ssh(
276 host=vnf_cfg['mgmt_ip_address'],
277 port=vnf_cfg['port'],
278 username=vnf_cfg['username'],
279 password=vnf_cfg['password'],
280 allow_agent=False,
281 look_for_keys=False,
282 hostkey_verify=False,
283 )
284
285 yield from self._parent.update_vnf_state(vnf_cfg, conmanY.RecordState.NETCONF_SSH_CONNECTED)
286 self._log.debug("netconf over SSH connected to VNF: %s", log_this_vnf(vnf_cfg))
287 return
288
289 except ncclient.transport.errors.SSHError as e:
290 yield from self._parent.update_vnf_state(vnf_cfg, conmanY.RecordState.FAILED_CONNECTION)
291 self._log.error("Netconf connection to VNF: %s, failed: %s",
292 log_this_vnf(vnf_cfg), str(e))
293
294 yield from asyncio.sleep(2, loop=self._loop)
295
296 raise ConfigManagerROifConnectionError(
297 "Failed to connect to VNF: %s within %s seconds" %
298 (log_this_vnf(vnf_cfg), timeout_secs)
299 )
300
301 @asyncio.coroutine
302 def apply_edit_cfg(self):
303 vnf_cfg = self._vnf_cfg
304 self._log.debug("Attempting to apply netconf to VNF: %s", log_this_vnf(vnf_cfg))
305
306 if self._manager is None:
307 self._log.error("Netconf is not connected to VNF: %s, aborting!", log_this_vnf(vnf_cfg))
308 return
309
310 # Get config file contents
311 try:
312 with open(vnf_cfg['cfg_file']) as f:
313 configuration = f.read()
314 except Exception as e:
315 self._log.error("Reading contents of the configuration file(%s) failed: %s", vnf_cfg['cfg_file'], str(e))
316 return
317
318 try:
319 self._log.debug("apply_edit_cfg to VNF: %s", log_this_vnf(vnf_cfg))
320 xml = '<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">{}</config>'.format(configuration)
321 response = yield from self._manager.edit_config(xml, target='running')
322 if hasattr(response, 'xml'):
323 response_xml = response.xml
324 else:
325 response_xml = response.data_xml.decode()
326
327 self._log.debug("apply_edit_cfg response: %s", response_xml)
328 if '<rpc-error>' in response_xml:
329 raise ConfigManagerROifConnectionError("apply_edit_cfg response has rpc-error : %s",
330 response_xml)
331
332 self._log.debug("apply_edit_cfg Successfully applied configuration {%s}", xml)
333 except:
334 raise
335
336 class ConfigManagerVNFjujuconf(object):
337
338 def __init__(self, log, loop, parent, vnf_cfg):
339 self._log = log
340 self._loop = loop
341 self._parent = parent
342 self._manager = None
343 self._vnf_cfg = vnf_cfg
344
345 #@asyncio.coroutine
346 def apply_edit_cfg(self):
347 vnf_cfg = self._vnf_cfg
348 self._log.debug("Attempting to apply juju conf to VNF: %s", log_this_vnf(vnf_cfg))
349 try:
350 args = ['python3',
351 vnf_cfg['juju_script'],
352 '--server', vnf_cfg['mgmt_ip_address'],
353 '--user', vnf_cfg['user'],
354 '--password', vnf_cfg['secret'],
355 '--port', str(vnf_cfg['port']),
356 vnf_cfg['cfg_file']]
357 self._log.error("juju script command (%s)", args)
358
359 proc = yield from asyncio.create_subprocess_exec(
360 *args,
361 stdout=asyncio.subprocess.PIPE)
362 juju_msg = yield from proc.stdout.read()
363 rc = yield from proc.wait()
364
365 if rc != 0:
366 raise ScriptError(
367 "Juju config returned error code : %s" % rc
368 )
369
370 self._log.debug("Juju config output (%s)", juju_msg)
371 except Exception as e:
372 self._log.error("Error (%s) while executing juju config", str(e))
373 raise