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/>.
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. Since the version 5.4.1, the Topic Architecture start with OMF_5.4 instead of OMF used for OMF5.3
52 def __init__(self, slice, host, port, password, xmpp_root = None):
55 :param slice: Xmpp Slice
57 :param host: Xmpp Server
59 :param port: Xmpp Port
61 :param password: Xmpp password
63 :param xmpp_root: Root of the Xmpp Topic Architecture
67 super(OMFAPI, self).__init__("OMFAPI")
69 date = datetime.datetime.now().strftime("%Y-%m-%dt%H.%M.%S")
70 tz = -time.altzone if time.daylight != 0 else -time.timezone
71 date += "%+06.2f" % (tz / 3600) # timezone difference is in seconds
72 self._user = "%s-%s" % (slice, date)
76 self._password = password
78 self._xmpp_root = xmpp_root or "OMF_5.4"
85 if sys.version_info < (3, 0):
87 sys.setdefaultencoding('utf8')
89 # instantiate the xmpp client
92 # register xmpp nodes for the experiment
93 self._enroll_experiment()
94 self._enroll_newexperiment()
96 # register xmpp logger for the experiment
99 def _init_client(self):
100 """ Initialize XMPP Client
103 jid = "%s@%s" % (self._user, self._host)
104 xmpp = OMFClient(jid, self._password)
105 # PROTOCOL_SSLv3 required for compatibility with OpenFire
106 xmpp.ssl_version = ssl.PROTOCOL_SSLv3
108 if xmpp.connect((self._host, self._port)):
109 xmpp.process(block=False)
110 while not xmpp.ready:
113 self._message = MessageHandler(self._slice, self._user)
115 msg = "Unable to connect to the XMPP server."
117 raise RuntimeError(msg)
119 def _enroll_experiment(self):
120 """ Create and Subscribe to the Session Topic
123 xmpp_node = self._exp_session_id
124 self._client.create(xmpp_node)
125 #print "Create experiment sesion id topics !!"
126 self._client.subscribe(xmpp_node)
127 #print "Subscribe to experiment sesion id topics !!"
130 def _enroll_newexperiment(self):
131 """ Publish New Experiment Message
134 address = "/%s/%s/%s/%s" % (self._host, self._xmpp_root, self._slice, self._user)
136 payload = self._message.newexp_function(self._user, address)
137 slice_sid = "/%s/%s" % (self._xmpp_root, self._slice)
138 self._client.publish(payload, slice_sid)
140 def _enroll_logger(self):
141 """ Create and Subscribe to the Logger Topic
144 xmpp_node = self._logger_session_id
145 self._client.create(xmpp_node)
146 self._client.subscribe(xmpp_node)
148 payload = self._message.log_function("2",
149 "nodeHandler::NodeHandler",
151 "OMF Experiment Controller 5.4 (git 529a626)")
152 self._client.publish(payload, xmpp_node)
154 def _host_session_id(self, hostname):
155 """ Return the Topic Name as /xmpp_root/slice/user/hostname
157 :param hostname: Full hrn of the node
161 return "/%s/%s/%s/%s" % (self._xmpp_root, self._slice, self._user, hostname)
163 def _host_resource_id(self, hostname):
164 """ Return the Topic Name as /xmpp_root/slice/resources/hostname
166 :param hostname: Full hrn of the node
170 return "/%s/%s/resources/%s" % (self._xmpp_root, self._slice, hostname)
173 def _exp_session_id(self):
174 """ Return the Topic Name as /xmpp_root/slice/user
177 return "/%s/%s/%s" % (self._xmpp_root, self._slice, self._user)
180 def _logger_session_id(self):
181 """ Return the Topic Name as /xmpp_root/slice/LOGGER
184 return "/%s/%s/%s/LOGGER" % (self._xmpp_root, self._slice, self._user)
186 def delete(self, hostname):
187 """ Delete the topic corresponding to the hostname for this session
189 :param hostname: Full hrn of the node
193 if not hostname in self._hostnames:
196 self._hostnames.remove(hostname)
198 xmpp_node = self._host_session_id(hostname)
199 self._client.delete(xmpp_node)
201 def enroll_host(self, hostname):
202 """ Create and Subscribe to the session topic and the resources corresponding to the hostname
204 :param hostname: Full hrn of the node
208 if hostname in self._hostnames:
211 self._hostnames.append(hostname)
213 xmpp_node = self._host_session_id(hostname)
214 self._client.create(xmpp_node)
215 self._client.subscribe(xmpp_node)
217 xmpp_node = self._host_resource_id(hostname)
218 self._client.subscribe(xmpp_node)
220 payload = self._message.enroll_function("1", "*", "1", hostname)
221 self._client.publish(payload, xmpp_node)
223 def configure(self, hostname, attribute, value):
224 """ Configure attribute on the node
226 :param hostname: Full hrn of the node
228 :param attribute: Attribute that need to be configured (often written as /net/wX/attribute, with X the interface number)
230 :param value: Value of the attribute
234 payload = self._message.configure_function(hostname, value, attribute)
235 xmpp_node = self._host_session_id(hostname)
236 self._client.publish(payload, xmpp_node)
238 def execute(self, hostname, app_id, arguments, path, env):
239 """ Execute command on the node
241 :param hostname: Full hrn of the node
243 :param app_id: Application Id (Any id that represents in a unique way the application)
245 :param arguments: Arguments of the application
247 :param path: Path of the application
249 :param env: Environnement values for the application
253 payload = self._message.execute_function(hostname, app_id, arguments, path, env)
254 xmpp_node = self._host_session_id(hostname)
255 self._client.publish(payload, xmpp_node)
257 def exit(self, hostname, app_id):
258 """ Kill an application started with OMF
260 :param hostname: Full hrn of the node
262 :param app_id: Application Id of the application you want to stop
266 payload = self._message.exit_function(hostname, app_id)
267 xmpp_node = self._host_session_id(hostname)
268 self._client.publish(payload, xmpp_node)
270 def release(self, hostname):
271 """ Delete the session and logger topics. Then disconnect
274 if hostname in self._hostnames:
275 self.delete(hostname)
277 def disconnect(self) :
278 """ Delete the session and logger topics. Then disconnect
281 self._client.delete(self._exp_session_id)
282 self._client.delete(self._logger_session_id)
286 # Wait the send queue to be empty before disconnect
287 self._client.disconnect(wait=True)
288 msg = " Disconnected from XMPP Server"
292 class OMFAPIFactory(object):
296 It allows the different RM to use the same xmpp client if they use the same credentials.
297 For the moment, it is focused on Xmpp.
300 # use lock to avoid concurrent access to the Api list at the same times by 2 different threads
301 lock = threading.Lock()
305 def get_api(cls, slice, host, port, password):
308 :param slice: Xmpp Slice Name
310 :param host: Xmpp Server Adress
312 :param port: Xmpp Port (Default : 5222)
314 :param password: Xmpp Password
318 if slice and host and port and password:
319 key = cls._make_key(slice, host, port, password)
322 cls._apis[key]['cnt'] += 1
324 return cls._apis[key]['api']
326 omf_api = cls.create_api(slice, host, port, password)
332 def create_api(cls, slice, host, port, password):
333 """ Create an API if this one doesn't exist yet with this credentials
335 :param slice: Xmpp Slice Name
337 :param host: Xmpp Server Adress
339 :param port: Xmpp Port (Default : 5222)
341 :param password: Xmpp Password
345 omf_api = OMFAPI(slice, host, port, password)
346 key = cls._make_key(slice, host, port, password)
348 cls._apis[key]['api'] = omf_api
349 cls._apis[key]['cnt'] = 1
353 def release_api(cls, slice, host, port, password):
354 """ Release an API with this credentials
356 :param slice: Xmpp Slice Name
358 :param host: Xmpp Server Adress
360 :param port: Xmpp Port (Default : 5222)
362 :param password: Xmpp Password
366 if slice and host and port and password:
367 key = cls._make_key(slice, host, port, password)
369 cls._apis[key]['cnt'] -= 1
370 #print "Api Counter : " + str(cls._apis[key]['cnt'])
371 if cls._apis[key]['cnt'] == 0:
372 omf_api = cls._apis[key]['api']
377 def _make_key(cls, *args):
378 """ Hash the credentials in order to create a key
380 :param args: list of arguments used to create the hash (user, host, port, ...)
381 :type args: list of args
384 skey = "".join(map(str, args))
385 return hashlib.md5(skey).hexdigest()