2 # NEPI, a framework to manage network experiments
3 # Copyright (C) 2013 INRIA
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19 # Julien Tribino <julien.tribino@inria.fr>
27 from nepi.util.logger import Logger
29 from nepi.resources.omf.omf_client import OMFClient
30 from nepi.resources.omf.messages_5_4 import MessageHandler
34 .. class:: Class Args :
36 :param slice: Xmpp Slice
38 :param host: Xmpp Server
40 :param port: Xmpp Port
42 :param password: Xmpp password
44 :param xmpp_root: Root of the Xmpp Topic Architecture
49 This class is the implementation of an OMF 5.4 API.
50 Since the version 5.4.1, the Topic Architecture start with OMF_5.4
51 instead of OMF used for OMF5.3
54 def __init__(self, slice, host, port, password, xmpp_root = None,
58 :param slice: Xmpp Slice
60 :param host: Xmpp Server
62 :param port: Xmpp Port
64 :param password: Xmpp password
66 :param xmpp_root: Root of the Xmpp Topic Architecture
70 super(OMFAPI, self).__init__("OMFAPI")
72 self._user = "%s-%s" % (slice, self._exp_id)
76 self._password = password
78 self._xmpp_root = xmpp_root or "OMF_5.4"
86 if sys.version_info < (3, 0):
88 sys.setdefaultencoding('utf8')
90 # instantiate the xmpp client
93 # register xmpp nodes for the experiment
94 self._enroll_experiment()
95 self._enroll_newexperiment()
97 # register xmpp logger for the experiment
100 def _init_client(self):
101 """ Initialize XMPP Client
104 jid = "%s@%s" % (self._user, self._host)
105 xmpp = OMFClient(jid, self._password)
106 # PROTOCOL_SSLv3 required for compatibility with OpenFire
107 xmpp.ssl_version = ssl.PROTOCOL_SSLv3
109 if xmpp.connect((self._host, self._port)):
110 xmpp.process(block=False)
111 while not xmpp.ready:
114 self._message = MessageHandler(self._slice, self._user)
116 msg = "Unable to connect to the XMPP server."
118 raise RuntimeError(msg)
120 def _enroll_experiment(self):
121 """ Create and Subscribe to the Session Topic
124 xmpp_node = self._exp_session_id
125 self._client.create(xmpp_node)
126 #print "Create experiment sesion id topics !!"
127 self._client.subscribe(xmpp_node)
128 #print "Subscribe to experiment sesion id topics !!"
131 def _enroll_newexperiment(self):
132 """ Publish New Experiment Message
135 address = "/%s/%s/%s/%s" % (self._host, self._xmpp_root, self._slice,
138 payload = self._message.newexp_function(self._user, address)
139 slice_sid = "/%s/%s" % (self._xmpp_root, self._slice)
140 self._client.publish(payload, slice_sid)
142 def _enroll_logger(self):
143 """ Create and Subscribe to the Logger Topic
146 xmpp_node = self._logger_session_id
147 self._client.create(xmpp_node)
148 self._client.subscribe(xmpp_node)
150 payload = self._message.log_function("2",
151 "nodeHandler::NodeHandler",
153 "OMF Experiment Controller 5.4 (git 529a626)")
154 self._client.publish(payload, xmpp_node)
156 def _host_session_id(self, hostname):
157 """ Return the Topic Name as /xmpp_root/slice/user/hostname
159 :param hostname: Full hrn of the node
163 return "/%s/%s/%s/%s" % (self._xmpp_root, self._slice, self._user,
166 def _host_resource_id(self, hostname):
167 """ Return the Topic Name as /xmpp_root/slice/resources/hostname
169 :param hostname: Full hrn of the node
173 return "/%s/%s/resources/%s" % (self._xmpp_root, self._slice, hostname)
176 def _exp_session_id(self):
177 """ Return the Topic Name as /xmpp_root/slice/user
180 return "/%s/%s/%s" % (self._xmpp_root, self._slice, self._user)
183 def _logger_session_id(self):
184 """ Return the Topic Name as /xmpp_root/slice/LOGGER
187 return "/%s/%s/%s/LOGGER" % (self._xmpp_root, self._slice, self._user)
189 def delete(self, hostname):
190 """ Delete the topic corresponding to the hostname for this session
192 :param hostname: Full hrn of the node
196 if not hostname in self._hostnames:
199 self._hostnames.remove(hostname)
201 xmpp_node = self._host_session_id(hostname)
202 self._client.delete(xmpp_node)
204 def enroll_host(self, hostname):
205 """ Create and Subscribe to the session topic and the resources
206 corresponding to the hostname
208 :param hostname: Full hrn of the node
212 if hostname in self._hostnames:
215 self._hostnames.append(hostname)
217 xmpp_node = self._host_session_id(hostname)
218 self._client.create(xmpp_node)
219 self._client.subscribe(xmpp_node)
221 xmpp_node = self._host_resource_id(hostname)
222 self._client.subscribe(xmpp_node)
224 payload = self._message.enroll_function("1", "*", "1", hostname)
225 self._client.publish(payload, xmpp_node)
227 def configure(self, hostname, attribute, value):
228 """ Configure attribute on the node
230 :param hostname: Full hrn of the node
232 :param attribute: Attribute that need to be configured (
233 often written as /net/wX/attribute, with X the interface number)
235 :param value: Value of the attribute
239 payload = self._message.configure_function(hostname, value, attribute)
240 xmpp_node = self._host_session_id(hostname)
241 self._client.publish(payload, xmpp_node)
244 def send_stdin(self, hostname, value, app_id):
245 """ Send to the stdin of the application the value
247 :param hostname: Full hrn of the node
249 :param appid: Application Id (Any id that represents in a unique
252 :param value: parameter to execute in the stdin of the application
256 payload = self._message.stdin_function(hostname, value, app_id)
257 xmpp_node = self._host_session_id(hostname)
258 self._client.publish(payload, xmpp_node)
261 def execute(self, hostname, app_id, arguments, path, env):
262 """ Execute command on the node
264 :param hostname: Full hrn of the node
266 :param app_id: Application Id (Any id that represents in a unique
269 :param arguments: Arguments of the application
271 :param path: Path of the application
273 :param env: Environnement values for the application
277 payload = self._message.execute_function(hostname, app_id, arguments,
279 xmpp_node = self._host_session_id(hostname)
280 self._client.publish(payload, xmpp_node)
282 def exit(self, hostname, app_id):
283 """ Kill an application started with OMF
285 :param hostname: Full hrn of the node
287 :param app_id: Application Id of the application you want to stop
291 payload = self._message.exit_function(hostname, app_id)
292 xmpp_node = self._host_session_id(hostname)
293 self._client.publish(payload, xmpp_node)
295 def release(self, hostname):
296 """ Delete the session and logger topics. Then disconnect
299 if hostname in self._hostnames:
300 self.delete(hostname)
302 def disconnect(self) :
303 """ Delete the session and logger topics. Then disconnect
306 self._client.delete(self._exp_session_id)
307 self._client.delete(self._logger_session_id)
311 # Wait the send queue to be empty before disconnect
312 self._client.disconnect(wait=True)
313 msg = " Disconnected from XMPP Server"
317 class OMFAPIFactory(object):
321 It allows the different RM to use the same xmpp client if they use
322 the same credentials. For the moment, it is focused on XMPP.
325 # use lock to avoid concurrent access to the Api list at the same times by 2
327 lock = threading.Lock()
331 def get_api(cls, slice, host, port, password, exp_id = None):
334 :param slice: Xmpp Slice Name
336 :param host: Xmpp Server Adress
338 :param port: Xmpp Port (Default : 5222)
340 :param password: Xmpp Password
344 if slice and host and port and password:
345 key = cls._make_key(slice, host, port, password, exp_id)
348 #print "Api Counter : " + str(cls._apis[key]['cnt'])
349 cls._apis[key]['cnt'] += 1
351 return cls._apis[key]['api']
353 omf_api = cls.create_api(slice, host, port, password, exp_id)
359 def create_api(cls, slice, host, port, password, exp_id):
360 """ Create an OMF API if this one doesn't exist yet with this credentials
362 :param slice: Xmpp Slice Name
364 :param host: Xmpp Server Adress
366 :param port: Xmpp Port (Default : 5222)
368 :param password: Xmpp Password
372 omf_api = OMFAPI(slice, host, port, password, exp_id = exp_id)
373 key = cls._make_key(slice, host, port, password, exp_id)
375 cls._apis[key]['api'] = omf_api
376 cls._apis[key]['cnt'] = 1
380 def release_api(cls, slice, host, port, password, exp_id = None):
381 """ Release an OMF API with this credentials
383 :param slice: Xmpp Slice Name
385 :param host: Xmpp Server Adress
387 :param port: Xmpp Port (Default : 5222)
389 :param password: Xmpp Password
393 if slice and host and port and password:
394 key = cls._make_key(slice, host, port, password, exp_id)
396 cls._apis[key]['cnt'] -= 1
397 #print "Api Counter : " + str(cls._apis[key]['cnt'])
398 if cls._apis[key]['cnt'] == 0:
399 omf_api = cls._apis[key]['api']
404 def _make_key(cls, *args):
405 """ Hash the credentials in order to create a key
407 :param args: list of arguments used to create the hash (user, host, port, ...)
408 :type args: list of args
411 skey = "".join(map(str, args))
412 return hashlib.md5(skey).hexdigest()