55d48c894e12178029e829595f0d98629aec4d89
[nepi.git] / src / nepi / testbeds / omf / metadata.py
1 # -*- coding: utf-8 -*-
2
3 import functools
4 import random
5 import weakref
6
7 from constants import TESTBED_ID, TESTBED_VERSION
8 from nepi.core import metadata
9 from nepi.core.attributes import Attribute
10 from nepi.util import tags, validation, ipaddr2
11 from nepi.util.constants import ApplicationStatus as AS, \
12         FactoryCategories as FC, DeploymentConfiguration as DC
13
14 ##############################################################################
15
16 class OmfResource(object):
17     def __init__(self, guid, tc):
18         super(OmfResource, self).__init__()
19         self._tc = weakref.ref(tc)
20         self._guid = guid
21
22     @property
23     def tc(self):
24         return self._tc and self._tc()
25
26     @property
27     def guid(self):
28         return self._guid
29
30     def configure(self):
31         pass
32
33     def start(self):
34         pass
35
36     def stop(self):
37         pass
38
39     def status(self):
40         pass
41
42     def shutdown(self):
43         pass
44
45 ## NODE #######################################################################
46
47 class OmfNode(OmfResource):
48     def __init__(self, guid, tc):
49         super(OmfNode, self).__init__(guid, tc)
50         self.hostname = self.tc._get_parameters(guid)['hostname']
51         self.tc.api.enroll_host(self.hostname)
52
53     def configure(self):
54         routes = self.tc._add_route.get(self.guid, [])
55         iface_guids = self.tc.get_connected(self.guid, "devs", "node")
56        
57         for route in routes:
58             (destination, netprefix, nexthop, metric, device) = route
59             netmask = ipaddr2.ipv4_mask2dot(netprefix)
60
61             # Validate that the interface is associated to the node
62             for iface_guid in iface_guids:
63                 iface = self.tc.elements.get(iface_guid)
64                 if iface.devname == device:
65                     self.tc.api.execute(self.hostname, 
66                         "Id#%s" % str(random.getrandbits(128)), 
67                         "add -net %s netmask %s dev %s" % (destination, netmask, iface.devname), 
68                         "/sbin/route", # path
69                         None, # env
70                      )
71                     break
72
73 ## APPLICATION ################################################################
74
75 class OmfApplication(OmfResource):
76     def __init__(self, guid, tc):
77         super(OmfApplication, self).__init__(guid, tc)
78         node_guids = tc.get_connected(guid, "node", "apps")
79         if len(node_guids) == 0:
80             raise RuntimeError("Can't instantiate interface %d outside node" % guid)
81
82         self._node_guid = node_guids[0] 
83         self.app_id = None
84         self.arguments = None
85         self.path = None
86         self.env = None
87
88     def start(self):
89         node = self.tc.elements.get(self._node_guid)
90         self.tc.api.execute(node.hostname, 
91                 self.appId, 
92                 self.arguments, 
93                 self.path,
94                 self.env)
95
96     def stop(self):
97         node = self.tc.elements.get(self._node_guid)
98         self.tc.api.exit(node.hostname, 
99                 self.appId) 
100
101     def status(self):
102         if guid not in testbed_instance.elements.keys():
103             return AS.STATUS_NOT_STARTED
104         return AS.STATUS_RUNNING
105         # TODO!!!!
106         #return AS.STATUS_FINISHED
107
108
109 ## WIFIIFACE ########################################################
110
111 class OmfWifiInterface(OmfResource):
112     alias2name = dict({'w0':'wlan0', 'w1':'wlan1'})
113
114     def __init__(self, guid, tc):
115         super(OmfWifiInterface, self).__init__(guid, tc)
116         node_guids = tc.get_connected(guid, "node", "devs")
117         if len(node_guids) == 0:
118             raise RuntimeError("Can't instantiate interface %d outside node" % guid)
119
120         self._node_guid = node_guids[0] 
121         self.alias = self.tc._get_parameters(self.guid)['alias']
122         self.devname = self.alias2name.get(self.alias)
123         self.mode = None
124         self.type = None
125         self.essid = None
126         self.channel = None
127         self.ip = None
128
129     def __setattr__(self, name, value):
130         if name in ["ip", "mode", "type", "essid", "channel"]:
131             if value is not None:
132                 node = self.tc.elements.get(self._node_guid)    
133                 attribute = "net/%s/%s" % (self.alias, name)
134                 self._tc().api.configure(node.hostname, attribute, value)
135         
136         super(OmfWifiInterface, self).__setattr__(name, value)
137
138 # Factories
139 NODE = "Node"
140 WIFIIFACE = "WifiInterface"
141 CHANNEL = "Channel"
142 OMFAPPLICATION = "OmfApplication"
143
144 def create(factory, testbed_instance, guid):
145     clazz = OmfResource
146     if factory == NODE:
147         clazz = OmfNode
148     elif factory == OMFAPPLICATION:
149         clazz = OmfApplication
150     elif factory == WIFIIFACE:
151         clazz = OmfWifiInterface
152
153     element = clazz(guid, testbed_instance)
154     #import pdb; pdb.set_trace()
155     testbed_instance._elements[guid] = element
156
157 def start(testbed_instance, guid):
158     element = testbed_instance.elements.get(guid)
159     element.start()
160
161 def stop(testbed_instance, guid):
162     element = testbed_instance.elements.get(guid)
163     element.stop()
164
165 def status(testbed_instance, guid):
166     element = testbed_instance.elements.get(guid)
167     return element.status()
168
169 def configure(testbed_instance, guid):
170     element = testbed_instance.elements.get(guid)
171     return element.configure()
172
173 ### Factory information ###
174
175 connector_types = dict({
176     "apps": dict({
177                 "help": "Connector from node to applications", 
178                 "name": "apps",
179                 "max": -1, 
180                 "min": 0
181             }),
182     "devs": dict({
183                 "help": "Connector to network interfaces", 
184                 "name": "devs",
185                 "max": -1, 
186                 "min": 0
187             }),
188     "chan": dict({
189                 "help": "Connector from a device to a channel", 
190                 "name": "chan",
191                 "max": 1, 
192                 "min": 1
193             }),
194     "node": dict({
195                 "help": "Connector to a Node", 
196                 "name": "node",
197                 "max": 1, 
198                 "min": 1
199             }),
200    })
201
202 connections = [
203     dict({
204         "from": (TESTBED_ID, NODE, "devs"),
205         "to":   (TESTBED_ID, WIFIIFACE, "node"),
206         "can_cross": False
207     }),
208     dict({
209         "from": (TESTBED_ID, WIFIIFACE, "chan"),
210         "to":   (TESTBED_ID, CHANNEL, "devs"),
211         "can_cross": False
212     }),
213     dict({
214         "from": (TESTBED_ID, NODE, "apps"),
215         "to":   (TESTBED_ID, OMFAPPLICATION, "node"),
216         "can_cross": False
217     }),
218  ]
219
220 attributes = dict({
221     "appId": dict({
222                 "name": "appId",
223                 "help": "Application id",
224                 "type": Attribute.STRING,
225                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
226                 "validation_function": validation.is_string
227             }),
228     "arguments": dict({
229                 "name": "arguments",
230                 "help": "Application arguments",
231                 "type": Attribute.STRING,
232                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
233                 "validation_function": validation.is_string
234             }),
235     "path": dict({
236                 "name": "path",
237                 "help": "Path to binary (e.g '/opt/vlc-1.1.13/vlc')",
238                 "type": Attribute.STRING,
239                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
240                 "validation_function": validation.is_string
241             }),
242     "env": dict({
243                 "name": "env",
244                 "help": "String with space separated values of environment variables to set before starting application (e.g 'FOO=foo BAR=bar')",
245                 "type": Attribute.STRING,
246                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
247                 "validation_function": validation.is_string
248             }),
249     "hostname": dict({
250                 "name": "hostname",
251                 "help": "Hostname for the target OMF node",
252                 "type": Attribute.STRING,
253                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
254                 "validation_function": validation.is_string
255             }),
256     "mode": dict({
257                 "name": "mode",
258                 "help": "Corresponds to the OMF attributes net/w0/mode",
259                 "type": Attribute.STRING,
260                 "flags": Attribute.NoDefaultValue, 
261                 "validation_function": validation.is_string
262             }),
263     "type": dict({
264                 "name": "type",
265                 "help": "Corresponds to the OMF attributes net/w0/type",
266                 "type": Attribute.STRING,
267                 "flags": Attribute.NoDefaultValue, 
268                 "validation_function": validation.is_string
269             }),
270     "channel": dict({
271                 "name": "channel",
272                 "help": "Corresponds to the OMF attributes net/w0/channel",
273                 "type": Attribute.STRING,
274                 "flags": Attribute.NoDefaultValue, 
275                 "validation_function": validation.is_string
276             }),
277     "essid": dict({
278                 "name": "essid",
279                 "help": "Corresponds to the OMF attributes net/w0/essid",
280                 "type": Attribute.STRING,
281                 "flags": Attribute.NoDefaultValue, 
282                 "validation_function": validation.is_string
283             }),
284     "ip": dict({
285                 "name": "ip",
286                 "help": "Corresponds to the OMF attributes net/w0/ip",
287                 "type": Attribute.STRING,
288                 "flags": Attribute.NoDefaultValue, 
289                 "validation_function": validation.is_ip4_address
290             }),
291     "alias": dict({
292                 "name": "alias",
293                 "help": "Alias for device (e.g. w0, w1, etc)",
294                 "type": Attribute.STRING,
295                 "value": "w0",
296                 "flags": Attribute.NoDefaultValue, 
297                 "validation_function": validation.is_string
298             }),
299     })
300
301 traces = dict()
302
303 create_order = [ NODE, WIFIIFACE, CHANNEL, OMFAPPLICATION ]
304 configure_order = [ WIFIIFACE,  NODE, CHANNEL, OMFAPPLICATION ]
305
306 factories_info = dict({
307     NODE: dict({
308             "help": "OMF Node",
309             "category": FC.CATEGORY_NODES,
310             "create_function": functools.partial(create, NODE),
311             "configure_function": configure,
312             "box_attributes": ["hostname"],
313             "connector_types": ["devs", "apps"],
314             "tags": [tags.NODE, tags.ALLOW_ROUTES],
315        }),
316     WIFIIFACE: dict({
317             "help": "Wireless network interface",
318             "category": FC.CATEGORY_DEVICES,
319             "create_function": functools.partial(create, WIFIIFACE),
320             "configure_function": configure,
321             "box_attributes": ["mode", "type", "channel", "essid", "ip", "alias"],
322             "connector_types": ["node", "chan"],
323             "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
324        }),
325     CHANNEL: dict({
326             "help": "Wireless channel",
327             "category": FC.CATEGORY_DEVICES,
328             "create_function": create,
329             "create_function": functools.partial(create, CHANNEL),
330             "box_attributes": ["mode", "type", "channel", "essid"],
331             "connector_types": ["devs"],
332        }),
333     OMFAPPLICATION: dict({
334             "help": "Generic executable command line application",
335             "category": FC.CATEGORY_APPLICATIONS,
336             "create_function": functools.partial(create, OMFAPPLICATION),
337             "start_function": start,
338             "stop_function": stop,
339             "status_function": status,
340             "box_attributes": ["appId", "arguments", "path", "env"],
341             "connector_types": ["node"],
342             "tags": [tags.APPLICATION],
343         }),
344 })
345
346 testbed_attributes = dict({
347     "enable_debug": dict({
348             "name": "enableDebug",
349             "help": "Enable netns debug output",
350             "type": Attribute.BOOL,
351             "value": False,
352             "validation_function": validation.is_bool
353         }),
354     "xmppSlice": dict({
355                 "name": "xmppSlice",
356                 "help": "OMF slice",
357                 "type": Attribute.STRING,
358                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
359                 "validation_function": validation.is_string
360             }),
361     "xmppHost": dict({
362                 "name": "xmppHost",
363                 "help": "OMF XMPP server host",
364                 "type": Attribute.STRING,
365                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
366                 "validation_function": validation.is_string
367             }),
368     "xmppPort": dict({
369                 "name": "xmppPort",
370                 "help": "OMF XMPP service port",
371                 "type": Attribute.INTEGER,
372                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
373                 "validation_function": validation.is_integer
374             }),
375     "xmppPassword": dict({
376                 "name": "xmppPassword",
377                 "help": "OMF XMPP slice password",
378                 "type": Attribute.STRING,
379                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
380                 "validation_function": validation.is_string
381             }),
382     "xmppRoot": dict({
383                 "name": "xmppRoot",
384                 "help": "Root node of the xmpp server pubsub tree",
385                 "type": Attribute.STRING,
386                 "value": "OMF",
387                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
388                 "validation_function": validation.is_string
389             }),
390     })
391
392 supported_recovery_policies = [
393         DC.POLICY_FAIL,
394     ]
395
396 class MetadataInfo(metadata.MetadataInfo):
397     @property
398     def connector_types(self):
399         return connector_types
400
401     @property
402     def connections(self):
403         return connections
404
405     @property
406     def attributes(self):
407         return attributes
408
409     @property
410     def traces(self):
411         return traces
412
413     @property
414     def create_order(self):
415         return create_order
416
417     @property
418     def configure_order(self):
419         return configure_order
420
421     @property
422     def factories_info(self):
423         return factories_info
424
425     @property
426     def testbed_attributes(self):
427         return testbed_attributes
428
429     @property
430     def testbed_id(self):
431         return TESTBED_ID
432
433     @property
434     def testbed_version(self):
435         return TESTBED_VERSION
436     
437     @property
438     def supported_recover_policies(self):
439         return supported_recovery_policies
440