Several execution fixes:
[nepi.git] / src / nepi / testbeds / planetlab / node.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from constants import TESTBED_ID
5 import plcapi
6 import operator
7
8 class Node(object):
9     BASEFILTERS = {
10         # Map Node attribute to plcapi filter name
11         'hostname' : 'hostname',
12     }
13     
14     TAGFILTERS = {
15         # Map Node attribute to (<tag name>, <plcapi filter expression>)
16         #   There are replacements that are applied with string formatting,
17         #   so '%' has to be escaped as '%%'.
18         'architecture' : ('arch','value'),
19         'operating_system' : ('fcdistro','value'),
20         'pl_distro' : ('pldistro','value'),
21         'min_reliability' : ('reliability%(timeframe)s', ']value'),
22         'max_reliability' : ('reliability%(timeframe)s', '[value'),
23         'min_bandwidth' : ('bw%(timeframe)s', ']value'),
24         'max_bandwidth' : ('bw%(timeframe)s', '[value'),
25     }    
26     
27     def __init__(self, api=None):
28         if not api:
29             api = plcapi.PLCAPI()
30         self._api = api
31         
32         # Attributes
33         self.hostname = None
34         self.architecture = None
35         self.operating_system = None
36         self.pl_distro = None
37         self.site = None
38         self.emulation = None
39         self.min_reliability = None
40         self.max_reliability = None
41         self.min_bandwidth = None
42         self.max_bandwidth = None
43         self.min_num_external_ifaces = None
44         self.max_num_external_ifaces = None
45         self.timeframe = 'm'
46         
47         # Those are filled when an actual node is allocated
48         self._node_id = None
49     
50     def build_filters(self, target_filters, filter_map):
51         for attr, tag in filter_map.iteritems():
52             value = getattr(self, attr, None)
53             if value is not None:
54                 target_filters[tag] = value
55         return target_filters
56     
57     @property
58     def applicable_filters(self):
59         has = lambda att : getattr(self,att,None) is not None
60         return (
61             filter(has, self.BASEFILTERS.iterkeys())
62             + filter(has, self.TAGFILTERS.iterkeys())
63         )
64     
65     def find_candidates(self, filter_slice_id=None):
66         fields = ('node_id',)
67         replacements = {'timeframe':self.timeframe}
68         
69         # get initial candidates (no tag filters)
70         basefilters = self.build_filters({}, self.BASEFILTERS)
71         if filter_slice_id:
72             basefilters['|slice_ids'] = (filter_slice_id,)
73         
74         # keyword-only "pseudofilters"
75         extra = {}
76         if self.site:
77             extra['peer'] = self.site
78             
79         candidates = set(map(operator.itemgetter('node_id'), 
80             self._api.GetNodes(filters=basefilters, fields=fields, **extra)))
81         
82         # filter by tag, one tag at a time
83         applicable = self.applicable_filters
84         for tagfilter in self.TAGFILTERS.iteritems():
85             attr, (tagname, expr) = tagfilter
86             
87             # don't bother if there's no filter defined
88             if attr in applicable:
89                 tagfilter = basefilters.copy()
90                 tagfilter['tagname'] = tagname % replacements
91                 tagfilter[expr % replacements] = getattr(self,attr)
92                 tagfilter['node_id'] = list(candidates)
93                 
94                 candidates &= set(map(operator.itemgetter('node_id'),
95                     self._api.GetNodeTags(filters=tagfilter, fields=fields)))
96         
97         # filter by iface count
98         if self.min_num_external_ifaces is not None or self.max_num_external_ifaces is not None:
99             # fetch interfaces for all, in one go
100             filters = basefilters.copy()
101             filters['node_id'] = list(candidates)
102             ifaces = dict(map(operator.itemgetter('node_id','interface_ids'),
103                 self._api.GetNodes(filters=basefilters, fields=('node_id','interface_ids')) ))
104             
105             # filter candidates by interface count
106             if self.min_num_external_ifaces is not None and self.max_num_external_ifaces is not None:
107                 predicate = ( lambda node_id : 
108                     self.min_num_external_ifaces <= len(ifaces.get(node_id,())) <= self.max_num_external_ifaces )
109             elif self.min_num_external_ifaces is not None:
110                 predicate = ( lambda node_id : 
111                     self.min_num_external_ifaces <= len(ifaces.get(node_id,())) )
112             else:
113                 predicate = ( lambda node_id : 
114                     len(ifaces.get(node_id,())) <= self.max_num_external_ifaces )
115             
116             candidates = set(filter(predicate, candidates))
117             
118         return candidates
119
120     def assign_node_id(self, node_id):
121         self._node_id = node_id
122         self.fetch_node_info()
123     
124     def fetch_node_info(self):
125         info = self._api.GetNodes(self._node_id)
126         tags = dict( (t['tagname'],t['value'])
127                      for t in self._api.GetNodeTags(node_id=self._node_id, fields=('tagname','value')) )
128
129         self.min_num_external_ifaces = None
130         self.max_num_external_ifaces = None
131         self.timeframe = 'm'
132         
133         replacements = {'timeframe':self.timeframe}
134         for attr, tag in self.BASEFILTERS.iteritems():
135             if tag in info:
136                 value = info[tag]
137                 setattr(self, attr, value)
138         for attr, (tag,_) in self.TAGFILTERS.iteritems():
139             tag = tag % replacements
140             if tag in tags:
141                 value = tags[tag]
142                 setattr(self, attr, value)
143         
144         if 'peer_id' in info:
145             self.site = self._api.peer_map[info['peer_id']]
146         
147         if 'interface_ids' in info:
148             self.min_num_external_ifaces = \
149             self.max_num_external_ifaces = len(info['interface_ids'])
150
151     def validate(self):
152         pass
153