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
32 from nepi.util.timefuncs import tsformat
36 .. class:: Class Args :
38 :param slice: Xmpp Slice
40 :param host: Xmpp Server
42 :param port: Xmpp Port
44 :param password: Xmpp password
46 :param xmpp_root: Root of the Xmpp Topic Architecture
51 This class is the implementation of an OMF 5.4 API. Since the version 5.4.1, the Topic Architecture start with OMF_5.4 instead of OMF used for OMF5.3
54 def __init__(self, slice, host, port, password, xmpp_root = None, exp_id = None):
57 :param slice: Xmpp Slice
59 :param host: Xmpp Server
61 :param port: Xmpp Port
63 :param password: Xmpp password
65 :param xmpp_root: Root of the Xmpp Topic Architecture
69 super(OMFAPI, self).__init__("OMFAPI")
71 tz = -time.altzone if time.daylight != 0 else -time.timezone
72 date += "%+06.2f" % (tz / 3600) # timezone difference is in seconds
73 self._exp_id = exp_id or date
74 self._user = "%s-%s" % (slice, self._exp_id)
78 self._password = password
80 self._xmpp_root = xmpp_root or "OMF_5.4"
87 if sys.version_info < (3, 0):
89 sys.setdefaultencoding('utf8')
91 # instantiate the xmpp client
94 # register xmpp nodes for the experiment
95 self._enroll_experiment()
96 self._enroll_newexperiment()
98 # register xmpp logger for the experiment
101 def _init_client(self):
102 """ Initialize XMPP Client
105 jid = "%s@%s" % (self._user, self._host)
106 xmpp = OMFClient(jid, self._password)
107 # PROTOCOL_SSLv3 required for compatibility with OpenFire
108 xmpp.ssl_version = ssl.PROTOCOL_SSLv3
110 if xmpp.connect((self._host, self._port)):
111 xmpp.process(block=False)
112 while not xmpp.ready:
115 self._message = MessageHandler(self._slice, self._user)
117 msg = "Unable to connect to the XMPP server."
119 raise RuntimeError(msg)
121 def _enroll_experiment(self):
122 """ Create and Subscribe to the Session Topic
125 xmpp_node = self._exp_session_id
126 self._client.create(xmpp_node)
127 #print "Create experiment sesion id topics !!"
128 self._client.subscribe(xmpp_node)
129 #print "Subscribe to experiment sesion id topics !!"
132 def _enroll_newexperiment(self):
133 """ Publish New Experiment Message
136 address = "/%s/%s/%s/%s" % (self._host, self._xmpp_root, self._slice, self._user)
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, hostname)
165 def _host_resource_id(self, hostname):
166 """ Return the Topic Name as /xmpp_root/slice/resources/hostname
168 :param hostname: Full hrn of the node
172 return "/%s/%s/resources/%s" % (self._xmpp_root, self._slice, hostname)
175 def _exp_session_id(self):
176 """ Return the Topic Name as /xmpp_root/slice/user
179 return "/%s/%s/%s" % (self._xmpp_root, self._slice, self._user)
182 def _logger_session_id(self):
183 """ Return the Topic Name as /xmpp_root/slice/LOGGER
186 return "/%s/%s/%s/LOGGER" % (self._xmpp_root, self._slice, self._user)
188 def delete(self, hostname):
189 """ Delete the topic corresponding to the hostname for this session
191 :param hostname: Full hrn of the node
195 if not hostname in self._hostnames:
198 self._hostnames.remove(hostname)
200 xmpp_node = self._host_session_id(hostname)
201 self._client.delete(xmpp_node)
203 def enroll_host(self, hostname):
204 """ Create and Subscribe to the session topic and the resources corresponding to the hostname
206 :param hostname: Full hrn of the node
210 if hostname in self._hostnames:
213 self._hostnames.append(hostname)
215 xmpp_node = self._host_session_id(hostname)
216 self._client.create(xmpp_node)
217 self._client.subscribe(xmpp_node)
219 xmpp_node = self._host_resource_id(hostname)
220 self._client.subscribe(xmpp_node)
222 payload = self._message.enroll_function("1", "*", "1", hostname)
223 self._client.publish(payload, xmpp_node)
225 def configure(self, hostname, attribute, value):
226 """ Configure attribute on the node
228 :param hostname: Full hrn of the node
230 :param attribute: Attribute that need to be configured (often written as /net/wX/attribute, with X the interface number)
232 :param value: Value of the attribute
236 payload = self._message.configure_function(hostname, value, attribute)
237 xmpp_node = self._host_session_id(hostname)
238 self._client.publish(payload, xmpp_node)
240 def execute(self, hostname, app_id, arguments, path, env):
241 """ Execute command on the node
243 :param hostname: Full hrn of the node
245 :param app_id: Application Id (Any id that represents in a unique way the application)
247 :param arguments: Arguments of the application
249 :param path: Path of the application
251 :param env: Environnement values for the application
255 payload = self._message.execute_function(hostname, app_id, arguments, path, env)
256 xmpp_node = self._host_session_id(hostname)
257 self._client.publish(payload, xmpp_node)
259 def exit(self, hostname, app_id):
260 """ Kill an application started with OMF
262 :param hostname: Full hrn of the node
264 :param app_id: Application Id of the application you want to stop
268 payload = self._message.exit_function(hostname, app_id)
269 xmpp_node = self._host_session_id(hostname)
270 self._client.publish(payload, xmpp_node)
272 def release(self, hostname):
273 """ Delete the session and logger topics. Then disconnect
276 if hostname in self._hostnames:
277 self.delete(hostname)
279 def disconnect(self) :
280 """ Delete the session and logger topics. Then disconnect
283 self._client.delete(self._exp_session_id)
284 self._client.delete(self._logger_session_id)
288 # Wait the send queue to be empty before disconnect
289 self._client.disconnect(wait=True)
290 msg = " Disconnected from XMPP Server"
294 class OMFAPIFactory(object):
298 It allows the different RM to use the same xmpp client if they use the same credentials.
299 For the moment, it is focused on Xmpp.
302 # use lock to avoid concurrent access to the Api list at the same times by 2 different threads
303 lock = threading.Lock()
307 def get_api(cls, slice, host, port, password, exp_id = None):
310 :param slice: Xmpp Slice Name
312 :param host: Xmpp Server Adress
314 :param port: Xmpp Port (Default : 5222)
316 :param password: Xmpp Password
320 if slice and host and port and password:
321 key = cls._make_key(slice, host, port, password, exp_id)
324 #print "Api Counter : " + str(cls._apis[key]['cnt'])
325 cls._apis[key]['cnt'] += 1
327 return cls._apis[key]['api']
329 omf_api = cls.create_api(slice, host, port, password, exp_id)
335 def create_api(cls, slice, host, port, password, exp_id):
336 """ Create an OMF API if this one doesn't exist yet with this credentials
338 :param slice: Xmpp Slice Name
340 :param host: Xmpp Server Adress
342 :param port: Xmpp Port (Default : 5222)
344 :param password: Xmpp Password
348 omf_api = OMFAPI(slice, host, port, password, exp_id = exp_id)
349 key = cls._make_key(slice, host, port, password, exp_id)
351 cls._apis[key]['api'] = omf_api
352 cls._apis[key]['cnt'] = 1
356 def release_api(cls, slice, host, port, password, exp_id = None):
357 """ Release an OMF API with this credentials
359 :param slice: Xmpp Slice Name
361 :param host: Xmpp Server Adress
363 :param port: Xmpp Port (Default : 5222)
365 :param password: Xmpp Password
369 if slice and host and port and password:
370 key = cls._make_key(slice, host, port, password, exp_id)
372 cls._apis[key]['cnt'] -= 1
373 #print "Api Counter : " + str(cls._apis[key]['cnt'])
374 if cls._apis[key]['cnt'] == 0:
375 omf_api = cls._apis[key]['api']
380 def _make_key(cls, *args):
381 """ Hash the credentials in order to create a key
383 :param args: list of arguments used to create the hash (user, host, port, ...)
384 :type args: list of args
387 skey = "".join(map(str, args))
388 return hashlib.md5(skey).hexdigest()