source: fact/tools/PyDimCtrl/factdimserver.py@ 14780

Last change on this file since 14780 was 14660, checked in by neise, 12 years ago
long time since last checkin
  • Property svn:executable set to *
File size: 14.8 KB
Line 
1#!/usr/bin/python -tti
2
3# for tab completion
4import rlcompleter
5import readline
6readline.parse_and_bind('tab: complete')
7
8import sys # for sys.exit() during bebugging mostly :-)
9import types # for dynamic class construction
10import time # for time.sleep
11from pprint import pprint # for nice printing
12
13from keyword import iskeyword # in case dynamic methods are equal to keywords
14
15import pydim # for the C-API Dim Call Wrappers
16
17# using this line makes 'export DIM_DNS_NODE=daq' obsolete
18pydim.dic_set_dns_node('daq')
19
20class FactDimServer( object ):
21 def __init__(self, name):
22 """ sets name of instance to name of server, all uppercase
23 """
24 self.list_of_states = []
25 self.name = name.upper()
26 self.print_state = False
27 self.print_msg = False
28 self.reg_state_cb()
29 self.reg_msg_cb()
30 self.user_func = None
31 self.__delay_between_cmds = 1.0
32 self.__delay_between_services = 1.0
33 self.__last_cmd_send = -1*float('inf')
34 self.__last_service_got = -1*float('inf')
35
36 def _cmd(self, cmdstr, *args):
37 """ used by all dynamicly created methods, which call a Dim CMD
38 """
39 cmdstr=self.name+'/'+cmdstr.upper()
40 desc = services[self.name][cmdstr.upper()][0]
41
42 # there is a work around for a bug in pydim
43 # even if a command needs no argument, and desc is also empty string
44 # one has to give one ... need to tell Niko about it.
45 if not desc:
46 desc = 'I'
47 args=(1,)
48 elif desc == 'O':
49 args = (0,)
50 while not time.time() - self.__last_cmd_send > self.__delay_between_cmds:
51 time.sleep(0.5)
52 self.__last_cmd_send = time.time()
53 pydim.dic_sync_cmnd_service(cmdstr, args, desc, timeout=1)
54
55
56
57 def _get(self, service):
58 """ used by all dynamicly created methods, which get a service
59 """
60 full_srv_name = self.name+'/'+service.upper()
61 desc = services[self.name][full_srv_name][0]
62
63 while not time.time() - self.__last_service_got > self.__delay_between_services:
64 time.sleep(0.5)
65 self.__last_service_got = time.time()
66 #print 'full_srv_name',full_srv_name
67 #print 'desc', desc
68 return pydim.dic_sync_info_service(full_srv_name, desc, timeout=1)
69
70
71
72 def __call__(self):
73 """ Wrapper / For Convenience
74 self.state() returns a string (if it exists)
75 *returns* numeric state code, parsed from return of self.state()
76 """
77 if hasattr(self, 'stn'):
78 return self.stn
79 else:
80 raise TypeError(self.name+' has no CMD called STATE')
81
82 def wait(self, state_num, timeout=None):
83 """ waits for a certain state
84 BLOCKING
85 returns True if state was reached
86 returns False if timeout occured
87 raises TypeError if Server has no method state
88 """
89
90 if not hasattr(self, 'stn'):
91 raise TypeError(self.name+' has no CMD called STATE')
92 if timeout == None:
93 timeout = float('inf')
94 else:
95 timeout = float(timeout)
96 start = time.time()
97 while not self.stn == state_num:
98 time.sleep(0.1)
99 if time.time() >= start+timeout:
100 return False
101 return True
102
103 def state_callback(self, state):
104 self.sts = state
105 try:
106 self.stn = int(state[state.find('[')+1 : state.find(']')])
107 except ValueError:
108 self.stn = None
109
110 self.last_st_change = time.time()
111 self.list_of_states.append( (self.last_st_change, self.stn) )
112 if len(self.list_of_states) > 10000:
113 print "list_of_states too long, truncating..."
114 self.list_of_states = self.list_of_states[1000:]
115
116 if self.user_func:
117 self.user_func( self.stn )
118
119 if self.print_state:
120 print state
121
122 def msg_callback(self, msg):
123 if self.print_msg:
124 print msg
125
126 def reg_state_cb(self):
127 if not hasattr(self, 'state'):
128 raise TypeError(self.name+' has no CMD called STATE')
129 service_name = self.name.upper()+'/STATE'
130 self.state_sid = pydim.dic_info_service(service_name, "C", self.state_callback)
131 if not self.state_sid:
132 del self.state_sid
133 raise IOError('could not register STATE client')
134
135 def reg_msg_cb(self):
136 if not hasattr(self, 'state'):
137 raise TypeError(self.name+' has no CMD called STATE')
138 service_name = self.name.upper()+'/MESSAGE'
139 self.msg_sid = pydim.dic_info_service(service_name, "C", self.msg_callback)
140 if not self.msg_sid:
141 del self.msg_sid
142 raise IOError('could not register MESSAGE client')
143
144 def unreg_state_cb(self):
145 if hasattr(self, 'state_sid'):
146 pydim.dic_release_service(self.state_sid)
147 del self.state_sid
148
149 def unreg_msg_cb(self):
150 if hasattr(self, 'msg_sid'):
151 pydim.dic_release_service(self.msg_sid)
152 del self.msg_sid
153
154 def __del__(self):
155 self.unreg_state_cb()
156 self.unreg_msg_cb()
157
158# utility functions for dynamic addid of methods to classes
159def add_command(cls, name):
160 meth_name = name.split('/')[1].lower()
161 if iskeyword(meth_name):
162 meth_name += '_cmd'
163
164 # this is the new command, it simple calls the _cmd() method
165 def new_command(self, *args):
166 self._cmd(meth_name, *args)
167
168 new_command.__name__ = meth_name
169
170 # from this line on, the docstring of the method is created
171 if name in dd:
172 if not dd[name]:
173 new_command.__doc__ = "DESC in SERVICE_DESC is empty ?!"
174 else:
175 new_command.__doc__ = dd[name]
176 else:
177 new_command.__doc__ = "-- no DESC found in SERVICE_DESC --"
178 new_command.__doc__ += '\n'
179 new_command.__doc__ += services[name.split('/')[0]][name][0]
180
181 # this line make the new_command() method, a method of the class cls
182 # giving it the name new_command.__name__
183 setattr( cls, new_command.__name__, new_command)
184
185# add_getter is very similar to add_command,
186# the only difference is, that it calls _get() instead of _cmd()
187# and since _get() has a return value, this return value is vorwarded to the user
188def add_getter(cls, name):
189 meth_name = name.split('/')
190 if len(meth_name) > 1:
191 meth_name = meth_name[1].lower()
192 elif len(meth_name) >0:
193 meth_name = meth_name[0].lower()
194 else:
195 print 'add_getter, cannot parse name:', name
196 raise ValueError('read above')
197 if iskeyword(meth_name):
198 meth_name += '_cmd'
199 def new_command(self):
200 return self._get(meth_name)
201 new_command.__name__ = meth_name
202 if name in dd:
203 if not dd[name]:
204 new_command.__doc__ = "DESC in SERVICE_DESC is empty ?!"
205 else:
206 new_command.__doc__ = dd[name]
207 else:
208 new_command.__doc__ = "-- no DESC found in SERVICE_DESC --"
209 new_command.__doc__ += '\n'
210 new_command.__doc__ += services[name.split('/')[0]][name][0]
211 setattr( cls, new_command.__name__, new_command)
212
213
214
215
216
217# In order to create classes according to the Dim-Servers, currently connected
218# to the DIS_DNS I have to parse DIS_DNS/SERVER_LIST
219# This is done in two steps, first I get the list of Server Names from DIS_DNS
220# and the I get the list of each servers services and cmds,
221# from each servers SERVICE_LIST and the service/command description
222# from each servers SERVICE_DESC
223# I get quite a lot of information, which I store in python dicts, or
224# even nested dicts, if necessary.
225
226def ParseDnsServerList():
227 # making server list
228 rawlist = pydim.dic_sync_info_service('DIS_DNS/SERVER_LIST','C', timeout=5)
229 if rawlist == None:
230 print "couldn't get the server list of DIS_DNS. program abortion..."
231 sys.exit(1)
232 #print rawlist
233 # the output needs to be treated a bit .. it is a tuple with only one long string in it
234 # the string contains | and the strange character \x00
235 # I use both to cut the list apart
236 rawlist = rawlist[0].split('\x00')
237 servers_n_hosts = rawlist[0].split('|')
238 server_ids = rawlist[1].split('|')
239
240 servers = {}
241 for i,snh in enumerate(servers_n_hosts):
242 snh = snh.split('@')
243 s = snh[0]
244 h = snh[1]
245 sid = server_ids[i]
246 servers[s] = (sid, h)
247
248 return servers
249
250
251
252# servers should be a dict containing uppercase server names as keys,
253# the values are not needed, so it might be any iteratable python listlike type
254# to be precise
255def ParseServersServiceList( servers ):
256
257 services = {}
258 dd = {}
259 for server in servers:
260 # sl_raw is a tuple, with an really long string, which needs to be parsed
261 sl_raw = pydim.dic_sync_info_service(server+'/SERVICE_LIST','C',timeout=3)[0]
262 if sl_raw == None:
263 print "couldn't get the service list of ", server, "program abortion..."
264 sys.exit(1)
265# print server
266# print sl_raw
267 # even without parsing, I can find out, if this server also gives me a
268 # service description list. In case it does not, this is fine as well
269 # the doc string of the dynamicly created methods, will then contain
270 # a note, that therer was no SERVICE_DESC ...
271 if server+'/SERVICE_DESC' in sl_raw:
272 sd_raw = pydim.dic_sync_info_service(server+'/SERVICE_DESC','C',timeout=3)[0]
273 if sd_raw == None:
274 print "couldn't get the service description list of ", server, "program abortion..."
275 sys.exit(1)
276 else:
277 sd_raw = ''
278
279 # now before parsing, I strip off all ASCII zeroes '\x00' and all
280 # line breaks off the *end* of the long string of both
281 # the service list sl
282 # and service description sd
283 #
284 # I think in sl_raw were alse some '\x00' in the middle .. these
285 # are replaced by nothing, in case they are there.
286 sl_raw = sl_raw.rstrip('\x00\n')
287 sl_raw = sl_raw.replace('\x00','')
288 sd_raw = sd_raw.rstrip('\x00\n')
289
290 # The lists are seperated by line breaks, so I split them using this
291 sl = sl_raw.split('\n')
292 sd = sd_raw.split('\n')
293
294 # First I parse the service descriptons, so I have them available,
295 # when I create the dict full of services.
296 # All desciptions look like this
297 # 'SERVER/SERVICE=some descriptive text' or
298 # 'SERVER/SERVICE='
299 # this I use to create the dictionary.
300 # create descripton dict dd from service_desc list sd
301 for d_str in sd:
302 service,equalsign,desc = d_str.partition('=')
303 if not '/' in service:
304 service = server+'/'+service
305 #if '=' != equalsign:
306 # print "Error: server:", server, "desc:", d_str
307 dd[service] = desc
308
309 # Now I fill ther services dict. Each server gets a nested dict
310 # inside services.
311 # Each service is explained in a string with a '|' in between.
312 # I use this for spliting.
313 # The string look like this
314 # SERVER/SERVICE|format-desc-str(e.g. I:2;C)|-empty- or CMD or RPC|
315 services[server] = {}
316 for service in sl:
317 service = service.split('|')
318 if service[0] in dd:
319 services[server][service[0]] = (
320 service[1], service[2], dd[service[0]])
321 return services, dd
322
323print 'requesting and parsing DIM DNS server list'
324servers = ParseDnsServerList()
325print '... parsing each servers service list'
326services, dd = ParseServersServiceList( servers )
327
328print 'creating classes'
329# create one class for each Fact Dim Server
330FactDimServerClasses = []
331for server_name in servers:
332 FactDimServerClasses.append(
333 types.ClassType( server_name, (FactDimServer,), {}) )
334 for cmd in services[server_name]:
335 cmdname = cmd.split('/')
336 if len(cmdname) > 1:
337 cmdname = cmdname[1]
338 elif len(cmdname) >0:
339 cmdname = cmdname[0]
340 cmd = server_name+'/'+cmdname
341 else:
342 print server_name, cmd
343 raise ValueError('was not able to parse service/command names')
344 if 'CMD' in services[server_name][cmd][1]:
345 add_command(FactDimServerClasses[-1], cmd)
346 elif not services[server_name][cmd][1]:
347 add_getter(FactDimServerClasses[-1], cmd)
348
349
350
351# create an instace of each of the classes
352# and make it globally known, i.e. known to the Python interpreter
353# all the ServerClass instances are collected in a list
354# so one can get a quick overview --> print dims
355print 'creating an instance of each FACT DIM server'
356
357dims = []
358new_instance = None
359for i,server_name in enumerate(servers):
360 if server_name == 'DIS_DNS':
361 continue
362 new_instance = FactDimServerClasses[i](server_name)
363 dims.append( new_instance )
364 globals()[server_name.lower()] = new_instance
365del new_instance
366del i
367
368print '.... fact DIM servers are ready to use'
369print '-'*70
370print
371##############################################################################
372# class for colored printing
373
374class bcolors:
375 HEADER = '\033[95m'
376 OKBLUE = '\033[94m'
377 OKGREEN = '\033[92m'
378 WARNING = '\033[93m'
379 FAIL = '\033[91m'
380 ENDC = '\033[0m'
381
382 def disable(self):
383 self.HEADER = ''
384 self.OKBLUE = ''
385 self.OKGREEN = ''
386 self.WARNING = ''
387 self.FAIL = ''
388 self.ENDC = ''
389
390##############################################################################
391# class which implements colored printing
392# method calls can be used instead of Python print calls
393# for conveniently printing colored output.
394
395
396class MSG( bcolors):
397 def __init__(self, verbose = True):
398 """ create MSG instance,
399 default is verbose,
400 sets self.output
401
402 if:
403 self.*output* = True, object behaves as expeted
404 if False, no call return anything
405 """
406 self.output = verbose
407
408 def fail(self, text ):
409 """ print in RED
410 """
411 text = str(text)
412 if self.output:
413 print bcolors.FAIL + "ERROR:" + bcolors.ENDC,
414 print bcolors.FAIL + text + bcolors.ENDC
415
416 def warn(self, text ):
417 """ print in YELLOW
418 """
419 text = str(text)
420 if self.output:
421 print bcolors.WARNING + text + bcolors.ENDC
422
423 def ok(self, text ):
424 """ print in GREEN
425 """
426 text = str(text)
427 if self.output:
428 print bcolors.OKGREEN + text + bcolors.ENDC
429
430 def __call__(self, *args):
431 """ print as Python print would do
432 """
433 if self.output:
434 for arg in args:
435 print arg,
436 print
437
Note: See TracBrowser for help on using the repository browser.