blob: 0f973bca77286cb72175429588c62698b2d5cd0a [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#
2# Copyright (c) 2012,2013 Big Switch Networks, Inc.
3#
4# Licensed under the Eclipse Public License, Version 1.0 (the
5# "License"); you may not use this file except in compliance with the
6# License. You may obtain a copy of the License at
7#
8# http://www.eclipse.org/legal/epl-v10.html
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied. See the License for the specific language governing
14# permissions and limitations under the License.
15#
16
17# The database/model descriptions exist to meet particular
18# needs, for example, switch-alias exist to provide an
19# alternate name space from dpid's, to allow for a more
20# readable and human usable form of the same dpid. Aliases
21# would then naturally need a alias->dpid conversion, and
22# at the same time, a dpid->alias (at least for the display
23# of dpid's).
24#
25# The functions in this file provide these type of abstractions,
26# taking the output from model lookup's in the rest api, and
27# supplying some service used by the cli.
28#
29
30import rest_to_model
31import fmtcnv
32import json
33import utif
34
35def init_midware(bs, modi):
36 global sdnsh, mi
37 sdnsh = bs
38 mi = modi
39
40#
41# --------------------------------------------------------------------------------
42
43def create_obj_type_dict(obj_type, field, key = None, value = None):
44 """
45 Return a dictionary from a table search, where the key is one of the
46 fields. This doesn't manage multiple field matches.
47
48 Typically, the field selected is a foreign key for the obj_type.
49
50 For ('host-network-address', 'host'), this creates a dict
51 indexed by the mac address, returning the row in the table associated
52 with the mac (since the primary key for 'host' is a mac address).
53
54 For ('tag-mapping', 'host'), this creates a dict indexed by
55 the mac, returning the matching row in the table.
56
57 note: This gets the whole table
58 """
59 if not mi.obj_type_has_field(obj_type, field):
60 return {}
61
62 if not mi.obj_type_has_model(obj_type):
63 data = {}
64 if key and value:
65 data[key] = value
66 rows = rest_to_model.get_model_from_url(obj_type, data)
67 elif not type(key) is dict:
68 try:
69 rows = sdnsh.get_table_from_store(obj_type, key, value)
70 except Exception, e:
71 errors = sdnsh.rest_error_to_dict(e)
72 print sdnsh.rest_error_dict_to_message(errors)
73 rows = []
74 else:
75 try:
76 rows = sdnsh.rest_query_objects(obj_type, key)
77 except Exception, e:
78 errors = sdnsh.rest_error_to_dict(e)
79 print sdnsh.rest_error_dict_to_message(errors)
80 rows = []
81
82 s_dict = {}
83 for row in rows:
84 if row[field] in s_dict:
85 s_dict[row[field]].append(row)
86 else:
87 s_dict[row[field]] = [row]
88
89 return s_dict
90
91
92#
93# ALIAS
94#
95
96#
97# --------------------------------------------------------------------------------
98
99def alias_lookup(alias_obj_type, alias_id):
100 """
101 Return the value for the alias replacement by looking it up in the store.
102 When there is no alias replacement, return None.
103 """
104 field = mi.alias_obj_type_field(alias_obj_type)
105 if not field:
106 print sdnsh.error_msg("Error: no field for alias")
107 return None
108 try:
109 alias_key = mi.pk(alias_obj_type)
110 # use an exact search instead of a 'get_object...()' since
111 # a miss for an exact search can lead to a 404 error, which
112 # gets recorded in the error logs
113 alias_row = sdnsh.get_table_from_store(alias_obj_type,
114 alias_key,
115 alias_id,
116 "exact")
117 if len(alias_row) == 1:
118 return alias_row[0][field]
119 # only len(alias_row) == 0 at this point
120 except:
121 pass
122 return None
123
124#
125# --------------------------------------------------------------------------------
126
127def convert_alias_to_object_key(obj_type, name_or_alias):
128 """
129 For a specific obj_type (table/model) which may have an alias 'row',
130 return the alias when it exists for this name_or_alias.
131 """
132 if obj_type in mi.alias_obj_type_xref:
133 if name_or_alias in sdnsh.reserved_words:
134 return name_or_alias
135 for alias in mi.alias_obj_type_xref[obj_type]:
136 alias_value = alias_lookup(alias, name_or_alias)
137 if alias_value:
138 return alias_value
139 return name_or_alias
140
141#
142# --------------------------------------------------------------------------------
143
144def alias_choices_for_alias_obj_type(entries, obj_type, text):
145 """
146 Used to return all choices of entries for an alias. Remove any original
147 items which appear in the entries list passed in preventing duplication
148 of entries
149
150 Also see cp_alias_choices(), which is similar, but includes
151 the current mode.
152 """
153 if obj_type in mi.alias_obj_type_xref:
154 for alias in mi.alias_obj_type_xref[obj_type]:
155 try:
156 key = mi.pk(alias)
157 alias_dict = create_obj_type_dict(alias, key, key, text)
158 #
159 # remove the alias name if the dpid is in the
160 # list of entries... In all cases the alias is added,
161 # especially since the alias_dict may only contain selected
162 # entries from the 'text' query, and entries may already
163 # exclude those items.
164 alias_field = mi.alias_obj_type_field(alias)
165 if not alias_field:
166 continue
167 for item in alias_dict:
168 if alias_dict[item][0][alias_field] in entries:
169 entries.remove(alias_dict[item][0][alias_field])
170 entries.append(item)
171
172 except Exception, e:
173 pass
174 return entries
175
176#
177# --------------------------------------------------------------------------------
178
179def alias_lookup_with_foreign_key(alias_obj_type, foreign_key):
180 """
181 Find the alias name for some alias based on the foreign key's
182 value it's associaed with.
183 """
184 foreign_field = mi.alias_obj_type_field(alias_obj_type)
185 try:
186 rows = sdnsh.get_table_from_store(alias_obj_type,
187 foreign_field,
188 foreign_key,
189 "exact")
190 except Exception, e:
191 errors = sdnsh.rest_error_to_dict(e)
192 print sdnsh.rest_error_dict_to_message(errors)
193 rows = []
194 if len(rows) == 1:
195 return rows[0][mi.pk(alias_obj_type)]
196 return None
197
198
199#
200# Interface between the cli and table output requires dictionaries
201# which map between low item type values (for example, dpid's) and
202# alias names for the items (for example, switch aliases), to be
203# updated before display. If cassandra could provide some version
204# number (or hash of the complete table), the lookup could be avoided
205# by valiating that the current result is up-to-date.
206#
207
208#
209# --------------------------------------------------------------------------------
210
211def update_show_alias(obj_type):
212 """
213 Update alias associations for the pretty printer, used for the
214 'show' of tables
215 """
216 if obj_type in mi.alias_obj_type_xref:
217 for alias in mi.alias_obj_type_xref[obj_type]:
218 field = mi.alias_obj_type_field(alias)
219 if not field:
220 print sdnsh.error_msg("update show alias alias_obj_type_field")
221 return
222 try:
223 table = sdnsh.get_table_from_store(alias)
224 except Exception, e:
225 table = []
226
227 new_dict = {}
228 key = mi.pk(alias)
229 # (foreign_obj, foreign_field) = \
230 # mi.foreign_key_references(alias, field)
231 for row in table:
232 new_dict[row[field]] = row[key]
233 fmtcnv.update_alias_dict(obj_type, new_dict)
234 return
235
236#
237# --------------------------------------------------------------------------------
238
239def update_switch_alias_cache():
240 """
241 Update the cliModeInfo prettyprinting switch table
242 """
243 return update_show_alias('switch-config')
244
245#
246# --------------------------------------------------------------------------------
247
248def update_switch_port_name_cache():
249 """
250 Update the cliModeInfo prettyprinting portNames table
251 """
252 # return update_show_alias('port')
253
254 errors = None
255 switch_port_to_name_dict = {}
256
257 try:
258 ports = rest_to_model.get_model_from_url('interfaces', {})
259 except Exception, e:
260 errors = sdnsh.rest_error_to_dict(e)
261
262 if errors:
263 print sdnsh.rest_error_dict_to_message(errors)
264 return
265
266 for port in ports:
267 key_string = port['switch'] + "." + "%d" % port['portNumber']
268 switch_port_to_name_dict[key_string] = port['portName']
269 fmtcnv.update_alias_dict("portNames", switch_port_to_name_dict)
270
271#
272# --------------------------------------------------------------------------------
273
274def update_host_alias_cache():
275 """
276 Update the cliModeInfo prettyprinting host table
277 """
278 return update_show_alias('host-config')
279
280
281#
282# --------------------------------------------------------------------------------
283# update_flow_cookie_hash
284
285def update_flow_cookie_hash():
286 """
287 The formatter keeps a map for static flow entries.
288 """
289 # iterate through all the static flows and get their hashes once
290 flow_map = {}
291 prime = 211
292 for sf in sdnsh.get_table_from_store("flow-entry"):
293 flow_hash = 2311
294 for i in range(0, len(sf['name'])):
295 flow_hash = flow_hash * prime + ord(sf['name'][i])
296 flow_hash = flow_hash & ( (1 << 20) - 1)
297 flow_map[flow_hash] = sf['name']
298
299 fmtcnv.update_alias_dict("staticflow", flow_map)
300
301 fmtcnv.callout_flow_encoders(sdnsh)
302
303
304#
305# --------------------------------------------------------------------------------
306#
307
308def update_controller_node_alias_cache():
309 return update_show_alias('controller-node')
310
311#
312# --------------------------------------------------------------------------------
313#
314def obj_type_show_alias_update(obj_type):
315 """
316 When some item is about to be displayed, particular 'alias'
317 items for the display may require updating. instead of just
318 updating everything all the time, peek at the different formatting
319 functions and use those function names to determine what needs to
320 be updated.
321
322 Also see formatter_to_update in climodelinfo, since it may
323 need to include new formatting functions.
324 """
325 update = {}
326 sdnsh.pp.format_to_alias_update(obj_type, update)
327
328 # select objects from 'update' dict
329 if 'host' in update:
330 update_host_alias_cache()
331 if 'switch' in update:
332 update_switch_alias_cache()
333 if 'port' in update:
334 update_switch_port_name_cache()
335 if 'flow' in update:
336 update_flow_cookie_hash()
337 if 'controller-node' in update:
338 update_controller_node_alias_cache()
339
340
341#
342# OBJECTs middleware.
343#
344
345
346#
347# --------------------------------------------------------------------------------
348
349def objects_starting_with(obj_type, text = "", key = None):
350 """
351 The function returns a list of matching keys from table/model
352 identified by the 'obj_type' parameter
353
354 If the table/model has a 'alias' field, then this field's
355 values are also examined for matches
356
357 The first argument is the name of a table/model in the store,
358 while the second argument is a prefix to filter the results.
359 The filter is applied to the key of the table/model, which
360 was previously populated.
361 """
362
363 if key:
364 if not mi.obj_type_has_field(obj_type, key):
365 sdnsh.warning("objects_starting_with: %s doesn't have field %s" %
366 (obj_type, key))
367 else:
368 key = mi.pk(obj_type)
369 if key == None:
370 sdnsh.warning("objects_starting_with: %s doesn't have pk" %
371 (obj_type))
372 key_entries = []
373
374 # Next, find the object
375 # Deal with any changes to the lookup name based on the 'contenation'
376 # of the config mode name to the named identifer.
377 #
378
379
380 case = mi.get_obj_type_field_case_sensitive(obj_type, key)
381 id_value = utif.convert_case(case, text)
382
383 if mi.obj_type_has_model(obj_type):
384 # from the database
385 try:
386 entries = sdnsh.get_table_from_store(obj_type, key, id_value)
387 errors = None
388 except Exception, e:
389 errors = sdnsh.rest_error_to_dict(e)
390 if errors:
391 print sdnsh.rest_error_dict_to_message(errors)
392 return key_entries
393 else:
394 if id_value == '':
395 entries = rest_to_model.get_model_from_url(obj_type, {})
396 else:
397 entries = rest_to_model.get_model_from_url(obj_type, { key + "__startswith" : id_value })
398
399 if key and entries:
400 # Expand any key values which are lists (hosts, for example)
401 items = [x[key] for x in entries if x.get(key)]
402 entries = []
403 for item in items:
404 if type(item) == list:
405 entries += item
406 else:
407 entries.append(item)
408 key_entries = [sdnsh.quote_item(obj_type, x)
409 for x in entries if x.startswith(id_value)]
410 #
411 # for some specific tables which have id's concatenated from multiple other
412 # components, only part of the id is available for completion.
413 #
414 if mi.is_compound_key(obj_type, key):
415 separator_character = mi.compound_key_separator(obj_type, key)
416 keyDict = {}
417 for key in key_entries:
418# keyDict[key.split(separator_character)[0]] = ''
419 keyDict[key] = ''
420 key_entries = keyDict.keys()
421
422 alias_obj_type = obj_type
423 if key != mi.pk(alias_obj_type):
424 # if this is a forgeign key, use the obj_type of the fk.
425 if mi.is_foreign_key(alias_obj_type, key):
426 (alias_obj_type, fk_name) = mi.foreign_key_references(alias_obj_type, key)
427 else:
428 # XXX possibly other choices to determine alias_obj_type?
429 alias_obj_type = None
430
431 if alias_obj_type:
432 obj_type_config = mi.obj_type_related_config_obj_type(alias_obj_type)
433
434 # alias_choices_for_alias_obj_type() removes switch dpid's which
435 # have associated alias names,
436 key_entries = alias_choices_for_alias_obj_type(key_entries,
437 obj_type_config,
438 text)
439
440 return key_entries
441
442
443#
444# --------------------------------------------------------------------------------
445
446def local_interfaces_firewall_open(protos, ports, controller_id = None):
447 """
448 Return a list of interfaces, which have the proto and port currently enabled
449
450 @param proto a string, or list of strings, identifying the protocol
451 @param port a strings, or list of strings or ints
452 """
453
454 # first collect all associated rules
455 if type(protos) != list:
456 protos = [protos]
457 if type(ports) != list:
458 ports = [ports]
459
460 rules = []
461 for proto in protos:
462 for port in ports:
463 query_dict = { 'proto' : proto, 'port' : port }
464 rules += sdnsh.rest_query_objects('firewall-rule', query_dict)
465
466 # create a dictionary indexed by the interface, which is part of the pk 'id'
467 rules_of_interface = dict([[x['interface'], x] for x in rules])
468
469 if controller_id == None:
470 # request 'this' controller
471 controller_url = "http://%s/rest/v1/system/controller" % sdnsh.controller
472 result = sdnsh.store.rest_simple_request(controller_url)
473 sdnsh.check_rest_result(result)
474 controller_id = json.loads(result)
475
476 if controller_id != 'all':
477 query_dict = { 'controller' : controller_id['id'] }
478
479 ifs = sdnsh.rest_query_objects('controller-interface', query_dict)
480
481 return [ifn for ifn in ifs if ifn['id'] in rules_of_interface]
482
483#
484# --------------------------------------------------------------------------------
485
486
487def log_url(ip_and_port = None, log = None):
488 """
489 Returns the url of the log's on the named ip_and_port.
490 """
491 log_path = 'http://%s/rest/v1/system/log' % ip_and_port
492 if log:
493 log_path += '/' + log
494 return log_path
495
496
497#
498# --------------------------------------------------------------------------------
499
500
501def controller_ip_and_port(controller):
502 """
503 Return a list of ip:port values for named controllers,
504 to use to build urls for REST API's. If a controller of 'all'
505 is passed in, then all the controllers ar enumerated.
506
507 If both port 80, and 8000 are open, then two ip:port
508 pairs will be returned for a controller. This returns
509 ALL values which match, not a single ip:port for each
510 controller.
511 """
512 url = 'http://%s/rest/v1/system/controller' % sdnsh.controller
513 rest_dict = sdnsh.rest_simple_request_to_dict(url)
514 this_controller = rest_dict['id']
515
516 ips_80 = [x for x in local_interfaces_firewall_open('tcp', 80,
517 controller)
518 if (x['ip'] != '' or x['discovered-ip'] != '')]
519
520 ips_8000 = [x for x in local_interfaces_firewall_open('tcp', 8000,
521 controller)
522 if (x['ip'] != '' or x['discovered-ip'] != '')]
523
524 return ['%s:80' % '127.0.0.1' if x['controller'] == this_controller else
525 x['discovered-ip'] if x['discovered-ip'] != '' else x['ip']
526 for x in ips_80] + ['%s:8000' %
527 '127.0.0.1' if x['controller'] == this_controller else
528 x['discovered-ip'] if x['discovered-ip'] != '' else x['ip']
529 for x in ips_8000]