-
Notifications
You must be signed in to change notification settings - Fork 6
/
httpserver.py
2156 lines (1979 loc) · 93.1 KB
/
httpserver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
##
# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
# This file is part of openmano
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# For those usages not covered by the Apache License, Version 2.0 please
# contact with: [email protected]
##
'''
This is the thread for the http server North API.
Two thread will be launched, with normal and administrative permissions.
'''
__author__="Alfonso Tierno"
__date__ ="$10-jul-2014 12:07:15$"
import bottle
import yaml
import json
import threading
import datetime
from utils import RADclass
from jsonschema import validate as js_v, exceptions as js_e
import host_thread as ht
from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \
tenant_edit_schema, \
flavor_new_schema, flavor_update_schema, \
image_new_schema, image_update_schema, \
server_new_schema, server_action_schema, network_new_schema, network_update_schema, \
port_new_schema, port_update_schema
global my
global url_base
global config_dic
url_base="/openvim"
HTTP_Bad_Request = 400
HTTP_Unauthorized = 401
HTTP_Not_Found = 404
HTTP_Forbidden = 403
HTTP_Method_Not_Allowed = 405
HTTP_Not_Acceptable = 406
HTTP_Request_Timeout = 408
HTTP_Conflict = 409
HTTP_Service_Unavailable = 503
HTTP_Internal_Server_Error= 500
def check_extended(extended, allow_net_attach=False):
'''Makes and extra checking of extended input that cannot be done using jsonschema
Attributes:
allow_net_attach: for allowing or not the uuid field at interfaces
that are allowed for instance, but not for flavors
Return: (<0, error_text) if error; (0,None) if not error '''
if "numas" not in extended: return 0, None
id_s=[]
numaid=0
for numa in extended["numas"]:
nb_formats = 0
if "cores" in numa:
nb_formats += 1
if "cores-id" in numa:
if len(numa["cores-id"]) != numa["cores"]:
return -HTTP_Bad_Request, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa["cores-id"]), numa["cores"],numaid)
id_s.extend(numa["cores-id"])
if "threads" in numa:
nb_formats += 1
if "threads-id" in numa:
if len(numa["threads-id"]) != numa["threads"]:
return -HTTP_Bad_Request, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa["threads-id"]), numa["threads"],numaid)
id_s.extend(numa["threads-id"])
if "paired-threads" in numa:
nb_formats += 1
if "paired-threads-id" in numa:
if len(numa["paired-threads-id"]) != numa["paired-threads"]:
return -HTTP_Bad_Request, "different number of paired-threads-id (%d) than paired-threads (%d) at numa %d" % (len(numa["paired-threads-id"]), numa["paired-threads"],numaid)
for pair in numa["paired-threads-id"]:
if len(pair) != 2:
return -HTTP_Bad_Request, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid)
id_s.extend(pair)
if nb_formats > 1:
return -HTTP_Service_Unavailable, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
#check interfaces
if "interfaces" in numa:
ifaceid=0
names=[]
vpcis=[]
for interface in numa["interfaces"]:
if "uuid" in interface and not allow_net_attach:
return -HTTP_Bad_Request, "uuid field is not allowed at numa %d interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
if "mac_address" in interface and interface["dedicated"]=="yes":
return -HTTP_Bad_Request, "mac_address can not be set for dedicated (passthrough) at numa %d, interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
if "name" in interface:
if interface["name"] in names:
return -HTTP_Bad_Request, "name repeated at numa %d, interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
names.append(interface["name"])
if "vpci" in interface:
if interface["vpci"] in vpcis:
return -HTTP_Bad_Request, "vpci %s repeated at numa %d, interface %s position %d" % (interface["vpci"], numaid, interface.get("name",""), ifaceid )
vpcis.append(interface["vpci"])
ifaceid+=1
numaid+=1
if numaid > 1:
return -HTTP_Service_Unavailable, "only one numa can be defined in this version "
for a in range(0,len(id_s)):
if a not in id_s:
return -HTTP_Bad_Request, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
return 0, None
#
# dictionaries that change from HTTP API to database naming
#
http2db_host={'id':'uuid'}
http2db_tenant={'id':'uuid'}
http2db_flavor={'id':'uuid','imageRef':'image_id'}
http2db_image={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
http2db_server={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
http2db_network={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
http2db_port={'id':'uuid', 'network_id':'net_id', 'mac_address':'mac', 'device_owner':'type','device_id':'instance_id','binding:switch_port':'switch_port','binding:vlan':'vlan', 'bandwidth':'Mbps'}
def remove_extra_items(data, schema):
deleted=[]
if type(data) is tuple or type(data) is list:
for d in data:
a= remove_extra_items(d, schema['items'])
if a is not None: deleted.append(a)
elif type(data) is dict:
for k in data.keys():
if 'properties' not in schema or k not in schema['properties'].keys():
del data[k]
deleted.append(k)
else:
a = remove_extra_items(data[k], schema['properties'][k])
if a is not None: deleted.append({k:a})
if len(deleted) == 0: return None
elif len(deleted) == 1: return deleted[0]
else: return deleted
def delete_nulls(var):
if type(var) is dict:
for k in var.keys():
if var[k] is None: del var[k]
elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
if delete_nulls(var[k]): del var[k]
if len(var) == 0: return True
elif type(var) is list or type(var) is tuple:
for k in var:
if type(k) is dict: delete_nulls(k)
if len(var) == 0: return True
return False
class httpserver(threading.Thread):
def __init__(self, db_conn, name="http", host='localhost', port=8080, admin=False, config_=None):
'''
Creates a new thread to attend the http connections
Attributes:
db_conn: database connection
name: name of this thread
host: ip or name where to listen
port: port where to listen
admin: if this has privileges of administrator or not
config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
'''
global url_base
global config_dic
#initialization
if config_ is not None:
config_dic = config_
if 'http_threads' not in config_dic:
config_dic['http_threads'] = {}
threading.Thread.__init__(self)
self.host = host
self.port = port
self.db = db_conn
self.admin = admin
if name in config_dic:
print "httpserver Warning!!! Onether thread with the same name", name
n=0
while name+str(n) in config_dic:
n +=1
name +=str(n)
self.name = name
self.url_preffix = 'http://' + self.host + ':' + str(self.port) + url_base
config_dic['http_threads'][name] = self
#Ensure that when the main program exits the thread will also exit
self.daemon = True
self.setDaemon(True)
def run(self):
bottle.run(host=self.host, port=self.port, debug=True) #quiet=True
def gethost(self, host_id):
result, content = self.db.get_host(host_id)
if result < 0:
print "httpserver.gethost error %d %s" % (result, content)
bottle.abort(-result, content)
elif result==0:
print "httpserver.gethost host '%s' not found" % host_id
bottle.abort(HTTP_Not_Found, content)
else:
data={'host' : content}
convert_boolean(content, ('admin_state_up',) )
change_keys_http2db(content, http2db_host, reverse=True)
print data['host']
return format_out(data)
@bottle.route(url_base + '/', method='GET')
def http_get():
print
return 'works' #TODO: put links or redirection to /openvim???
#
# Util funcions
#
def change_keys_http2db(data, http_db, reverse=False):
'''Change keys of dictionary data according to the key_dict values
This allow change from http interface names to database names.
When reverse is True, the change is otherwise
Attributes:
data: can be a dictionary or a list
http_db: is a dictionary with hhtp names as keys and database names as value
reverse: by default change is done from http API to database. If True change is done otherwise
Return: None, but data is modified'''
if type(data) is tuple or type(data) is list:
for d in data:
change_keys_http2db(d, http_db, reverse)
elif type(data) is dict or type(data) is bottle.FormsDict:
if reverse:
for k,v in http_db.items():
if v in data: data[k]=data.pop(v)
else:
for k,v in http_db.items():
if k in data: data[v]=data.pop(k)
def format_out(data):
'''return string of dictionary data according to requested json, yaml, xml. By default json'''
if 'application/yaml' in bottle.request.headers.get('Accept'):
bottle.response.content_type='application/yaml'
return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) #, canonical=True, default_style='"'
else: #by default json
bottle.response.content_type='application/json'
#return data #json no style
return json.dumps(data, indent=4) + "\n"
def format_in(schema):
try:
error_text = "Invalid header format "
format_type = bottle.request.headers.get('Content-Type', 'application/json')
if 'application/json' in format_type:
error_text = "Invalid json format "
#Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
client_data = json.load(bottle.request.body)
#client_data = bottle.request.json()
elif 'application/yaml' in format_type:
error_text = "Invalid yaml format "
client_data = yaml.load(bottle.request.body)
elif format_type == 'application/xml':
bottle.abort(501, "Content-Type: application/xml not supported yet.")
else:
print "HTTP HEADERS: " + str(bottle.request.headers.items())
bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
return
#if client_data == None:
# bottle.abort(HTTP_Bad_Request, "Content error, empty")
# return
#check needed_items
#print "HTTP input data: ", str(client_data)
error_text = "Invalid content "
js_v(client_data, schema)
return client_data
except (ValueError, yaml.YAMLError) as exc:
error_text += str(exc)
print error_text
bottle.abort(HTTP_Bad_Request, error_text)
except js_e.ValidationError as exc:
print "HTTP validate_in error, jsonschema exception ", exc.message, "at", exc.path
print " CONTENT: " + str(bottle.request.body.readlines())
error_pos = ""
if len(exc.path)>0: error_pos=" at '" + ":".join(map(str, exc.path)) + "'"
bottle.abort(HTTP_Bad_Request, error_text + error_pos+": "+exc.message)
#except:
# bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
# raise
def filter_query_string(qs, http2db, allowed):
'''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
Attributes:
'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
limit: limit dictated by user with the query string 'limit'. 100 by default
abort if not permitted, using bottel.abort
'''
where={}
limit=100
select=[]
if type(qs) is not bottle.FormsDict:
print '!!!!!!!!!!!!!!invalid query string not a dictionary'
#bottle.abort(HTTP_Internal_Server_Error, "call programmer")
else:
for k in qs:
if k=='field':
select += qs.getall(k)
for v in select:
if v not in allowed:
bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
elif k=='limit':
try:
limit=int(qs[k])
except:
bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
else:
if k not in allowed:
bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
if qs[k]!="null": where[k]=qs[k]
else: where[k]=None
if len(select)==0: select += allowed
#change from http api to database naming
for i in range(0,len(select)):
k=select[i]
if k in http2db:
select[i] = http2db[k]
change_keys_http2db(where, http2db)
#print "filter_query_string", select,where,limit
return select,where,limit
def convert_bandwidth(data, reverse=False):
'''Check the field bandwidth recursively and when found, it removes units and convert to number
It assumes that bandwidth is well formed
Attributes:
'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
'reverse': by default convert form str to int (Mbps), if True it convert from number to units
Return:
None
'''
if type(data) is dict:
for k in data.keys():
if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
convert_bandwidth(data[k], reverse)
if "bandwidth" in data:
try:
value=str(data["bandwidth"])
if not reverse:
pos = value.find("bps")
if pos>0:
if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000
elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000
else: data["bandwidth"]= int(data["bandwidth"][:pos-1])
else:
value = int(data["bandwidth"])
if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps"
else: data["bandwidth"]=str(value) + " Mbps"
except:
print "convert_bandwidth exception for type", type(data["bandwidth"]), " data", data["bandwidth"]
return
if type(data) is tuple or type(data) is list:
for k in data:
if type(k) is dict or type(k) is tuple or type(k) is list:
convert_bandwidth(k, reverse)
def convert_boolean(data, items):
'''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
It assumes that bandwidth is well formed
Attributes:
'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
'items': tuple of keys to convert
Return:
None
'''
if type(data) is dict:
for k in data.keys():
if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
convert_boolean(data[k], items)
if k in items:
if type(data[k]) is str:
if data[k]=="false": data[k]=False
elif data[k]=="true": data[k]=True
if type(data) is tuple or type(data) is list:
for k in data:
if type(k) is dict or type(k) is tuple or type(k) is list:
convert_boolean(k, items)
def convert_datetime2str(var):
'''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
It enters recursively in the dict var finding this kind of variables
'''
if type(var) is dict:
for k,v in var.items():
if type(v) is datetime.datetime:
var[k]= v.strftime('%Y-%m-%dT%H:%M:%S')
elif type(v) is dict or type(v) is list or type(v) is tuple:
convert_datetime2str(v)
if len(var) == 0: return True
elif type(var) is list or type(var) is tuple:
for v in var:
convert_datetime2str(v)
def check_valid_tenant(my, tenant_id):
if tenant_id=='any':
if not my.admin:
return HTTP_Unauthorized, "Needed admin privileges"
else:
result, _ = my.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id})
if result<=0:
return HTTP_Not_Found, "tenant '%s' not found" % tenant_id
return 0, None
def check_valid_uuid(uuid):
id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
try:
js_v(uuid, id_schema)
return True
except js_e.ValidationError:
return False
@bottle.error(400)
@bottle.error(401)
@bottle.error(404)
@bottle.error(403)
@bottle.error(405)
@bottle.error(406)
@bottle.error(408)
@bottle.error(409)
@bottle.error(503)
@bottle.error(500)
def error400(error):
e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
return format_out(e)
@bottle.hook('after_request')
def enable_cors():
#TODO: Alf: Is it needed??
bottle.response.headers['Access-Control-Allow-Origin'] = '*'
#
# HOSTS
#
@bottle.route(url_base + '/hosts', method='GET')
def http_get_hosts():
select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_host,
('id','name','description','status','admin_state_up') )
myself = config_dic['http_threads'][ threading.current_thread().name ]
result, content = myself.db.get_table(FROM='hosts', SELECT=select_, WHERE=where_, LIMIT=limit_)
if result < 0:
print "http_get_hosts Error", content
bottle.abort(-result, content)
else:
convert_boolean(content, ('admin_state_up',) )
change_keys_http2db(content, http2db_host, reverse=True)
for row in content:
row['links'] = ( {'href': myself.url_preffix + '/hosts/' + str(row['id']), 'rel': 'bookmark'}, )
data={'hosts' : content}
return format_out(data)
@bottle.route(url_base + '/hosts/<host_id>', method='GET')
def http_get_host_id(host_id):
my = config_dic['http_threads'][ threading.current_thread().name ]
return my.gethost(host_id)
@bottle.route(url_base + '/hosts', method='POST')
def http_post_hosts():
'''insert a host into the database. All resources are got and inserted'''
my = config_dic['http_threads'][ threading.current_thread().name ]
#check permissions
if not my.admin:
bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
#parse input data
http_content = format_in( host_new_schema )
r = remove_extra_items(http_content, host_new_schema)
if r is not None: print "http_post_host_id: Warning: remove extra items ", r
change_keys_http2db(http_content['host'], http2db_host)
host = http_content['host']
warning_text=""
if 'host-data' in http_content:
host.update(http_content['host-data'])
ip_name=http_content['host-data']['ip_name']
user=http_content['host-data']['user']
password=http_content['host-data'].get('password', None)
else:
ip_name=host['ip_name']
user=host['user']
password=host.get('password', None)
#fill rad info
rad = RADclass.RADclass()
(return_status, code) = rad.obtain_RAD(user, password, ip_name)
#return
if not return_status:
print 'http_post_hosts ERROR obtaining RAD', code
bottle.abort(HTTP_Bad_Request, code)
return
warning_text=code
rad_structure = yaml.load(rad.to_text())
print 'rad_structure\n---------------------'
print json.dumps(rad_structure, indent=4)
print '---------------------'
#return
WHERE_={"family":rad_structure['processor']['family'], 'manufacturer':rad_structure['processor']['manufacturer'], 'version':rad_structure['processor']['version']}
result, content = my.db.get_table(FROM='host_ranking',
SELECT=('ranking',),
WHERE=WHERE_)
if result > 0:
host['ranking'] = content[0]['ranking']
else:
#error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
#bottle.abort(HTTP_Bad_Request, error_text)
#return
warning_text += "Host " + str(WHERE_)+ " not found in ranking table. Assuming lowest value 100\n"
host['ranking'] = 100 #TODO: as not used in this version, set the lowest value
features = rad_structure['processor'].get('features', ())
host['features'] = ",".join(features)
host['numas'] = []
for node in (rad_structure['resource topology']['nodes'] or {}).itervalues():
interfaces= []
cores = []
eligible_cores=[]
count = 0
for core in node['cpu']['eligible_cores']:
eligible_cores.extend(core)
for core in node['cpu']['cores']:
for thread_id in core:
c={'core_id': count, 'thread_id': thread_id}
if thread_id not in eligible_cores: c['status'] = 'noteligible'
cores.append(c)
count = count+1
if 'nics' in node:
for port_k, port_v in node['nics']['nic 0']['ports'].iteritems():
if port_v['virtual']:
continue
else:
sriovs = []
for port_k2, port_v2 in node['nics']['nic 0']['ports'].iteritems():
if port_v2['virtual'] and port_v2['PF_pci_id']==port_k:
sriovs.append({'pci':port_k2, 'mac':port_v2['mac'], 'source_name':port_v2['source_name']})
if len(sriovs)>0:
#sort sriov according to pci and rename them to the vf number
new_sriovs = sorted(sriovs, key=lambda k: k['pci'])
index=0
for sriov in new_sriovs:
sriov['source_name'] = index
index += 1
interfaces.append ({'pci':str(port_k), 'Mbps': port_v['speed']/1000000, 'sriovs': new_sriovs, 'mac':port_v['mac'], 'source_name':port_v['source_name']})
#@TODO LA memoria devuelta por el RAD es incorrecta, almenos para IVY1, NFV100
memory=node['memory']['node_size'] / (1024*1024*1024)
#memory=get_next_2pow(node['memory']['hugepage_nr'])
host['numas'].append( {'numa_socket': node['id'], 'hugepages': node['memory']['hugepage_nr'], 'memory':memory, 'interfaces': interfaces, 'cores': cores } )
print json.dumps(host, indent=4)
#return
#
#insert in data base
result, content = my.db.new_host(host)
if result >= 0:
if content['admin_state_up']:
#create thread
host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
host_develop_mode = True if config_dic['mode']=='development' else False
host_develop_bridge_iface = config_dic.get('development_bridge', None)
thread = ht.host_thread(name=host.get('name',ip_name), user=user, host=ip_name, db=config_dic['db'], db_lock=config_dic['db_lock'],
test=host_test_mode, image_path=config_dic['image_path'],
version=config_dic['version'], host_id=content['uuid'],
develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface )
thread.start()
config_dic['host_threads'][ content['uuid'] ] = thread
#return host data
change_keys_http2db(content, http2db_host, reverse=True)
if len(warning_text)>0:
content["warning"]= warning_text
data={'host' : content}
return format_out(data)
else:
bottle.abort(HTTP_Bad_Request, content)
return
@bottle.route(url_base + '/hosts/<host_id>', method='PUT')
def http_put_host_id(host_id):
'''modify a host into the database. All resources are got and inserted'''
my = config_dic['http_threads'][ threading.current_thread().name ]
#check permissions
if not my.admin:
bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
#parse input data
http_content = format_in( host_edit_schema )
r = remove_extra_items(http_content, host_edit_schema)
if r is not None: print "http_post_host_id: Warning: remove extra items ", r
change_keys_http2db(http_content['host'], http2db_host)
#insert in data base
result, content = my.db.edit_host(host_id, http_content['host'])
if result >= 0:
convert_boolean(content, ('admin_state_up',) )
change_keys_http2db(content, http2db_host, reverse=True)
data={'host' : content}
#reload thread
config_dic['host_threads'][host_id].name = content.get('name',content['ip_name'])
config_dic['host_threads'][host_id].user = content['user']
config_dic['host_threads'][host_id].host = content['ip_name']
config_dic['host_threads'][host_id].insert_task("reload")
#print data
return format_out(data)
else:
bottle.abort(HTTP_Bad_Request, content)
return
@bottle.route(url_base + '/hosts/<host_id>', method='DELETE')
def http_delete_host_id(host_id):
my = config_dic['http_threads'][ threading.current_thread().name ]
#check permissions
if not my.admin:
bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
result, content = my.db.delete_row('hosts', host_id)
if result == 0:
bottle.abort(HTTP_Not_Found, content)
elif result >0:
#terminate thread
if host_id in config_dic['host_threads']:
config_dic['host_threads'][host_id].insert_task("exit")
#return data
data={'result' : content}
return format_out(data)
else:
print "http_delete_host_id error",result, content
bottle.abort(-result, content)
return
#
# TENANTS
#
@bottle.route(url_base + '/tenants', method='GET')
def http_get_tenants():
my = config_dic['http_threads'][ threading.current_thread().name ]
select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_tenant,
('id','name','description','enabled') )
result, content = my.db.get_table(FROM='tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
if result < 0:
print "http_get_tenants Error", content
bottle.abort(-result, content)
else:
change_keys_http2db(content, http2db_tenant, reverse=True)
convert_boolean(content, ('enabled',))
data={'tenants' : content}
#data['tenants_links'] = dict([('tenant', row['id']) for row in content])
return format_out(data)
@bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
def http_get_tenant_id(tenant_id):
my = config_dic['http_threads'][ threading.current_thread().name ]
result, content = my.db.get_table(FROM='tenants', SELECT=('uuid','name','description', 'enabled'),WHERE={'uuid': tenant_id} )
if result < 0:
print "http_get_tenant_id error %d %s" % (result, content)
bottle.abort(-result, content)
elif result==0:
print "http_get_tenant_id tenant '%s' not found" % tenant_id
bottle.abort(HTTP_Not_Found, "tenant %s not found" % tenant_id)
else:
change_keys_http2db(content, http2db_tenant, reverse=True)
convert_boolean(content, ('enabled',))
data={'tenant' : content[0]}
#data['tenants_links'] = dict([('tenant', row['id']) for row in content])
return format_out(data)
@bottle.route(url_base + '/tenants', method='POST')
def http_post_tenants():
'''insert a tenant into the database.'''
my = config_dic['http_threads'][ threading.current_thread().name ]
#parse input data
http_content = format_in( tenant_new_schema )
r = remove_extra_items(http_content, tenant_new_schema)
if r is not None: print "http_post_tenants: Warning: remove extra items ", r
change_keys_http2db(http_content['tenant'], http2db_tenant)
#insert in data base
result, content = my.db.new_tenant(http_content['tenant'])
if result >= 0:
return http_get_tenant_id(content)
else:
bottle.abort(-result, content)
return
@bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
def http_put_tenant_id(tenant_id):
'''update a tenant into the database.'''
my = config_dic['http_threads'][ threading.current_thread().name ]
#parse input data
http_content = format_in( tenant_edit_schema )
r = remove_extra_items(http_content, tenant_edit_schema)
if r is not None: print "http_put_tenant_id: Warning: remove extra items ", r
change_keys_http2db(http_content['tenant'], http2db_tenant)
#insert in data base
result, content = my.db.update_rows('tenants', http_content['tenant'], WHERE={'uuid': tenant_id}, log=True )
if result >= 0:
return http_get_tenant_id(tenant_id)
else:
bottle.abort(-result, content)
return
@bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
def http_delete_tenant_id(tenant_id):
my = config_dic['http_threads'][ threading.current_thread().name ]
#check permissions
r, tenants_flavors = my.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id','tenant_id'), WHERE={'tenant_id': tenant_id})
if r<=0:
tenants_flavors=()
r, tenants_images = my.db.get_table(FROM='tenants_images', SELECT=('image_id','tenant_id'), WHERE={'tenant_id': tenant_id})
if r<=0:
tenants_images=()
result, content = my.db.delete_row('tenants', tenant_id)
if result == 0:
bottle.abort(HTTP_Not_Found, content)
elif result >0:
print "alf", tenants_flavors, tenants_images
for flavor in tenants_flavors:
my.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
for image in tenants_images:
my.db.delete_row_by_key("images", "uuid", image['image_id'])
data={'result' : content}
return format_out(data)
else:
print "http_delete_tenant_id error",result, content
bottle.abort(-result, content)
return
#
# FLAVORS
#
@bottle.route(url_base + '/<tenant_id>/flavors', method='GET')
def http_get_flavors(tenant_id):
my = config_dic['http_threads'][ threading.current_thread().name ]
#check valid tenant_id
result,content = check_valid_tenant(my, tenant_id)
if result != 0:
bottle.abort(result, content)
#obtain data
select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
('id','name','description','public') )
if tenant_id=='any':
from_ ='flavors'
else:
from_ ='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
where_['tenant_id'] = tenant_id
result, content = my.db.get_table(FROM=from_, SELECT=select_, WHERE=where_, LIMIT=limit_)
if result < 0:
print "http_get_flavors Error", content
bottle.abort(-result, content)
else:
change_keys_http2db(content, http2db_flavor, reverse=True)
for row in content:
row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(row['id']) ) ), 'rel':'bookmark' } ]
data={'flavors' : content}
return format_out(data)
@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='GET')
def http_get_flavor_id(tenant_id, flavor_id):
my = config_dic['http_threads'][ threading.current_thread().name ]
#check valid tenant_id
result,content = check_valid_tenant(my, tenant_id)
if result != 0:
bottle.abort(result, content)
#obtain data
select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
if tenant_id=='any':
from_ ='flavors'
else:
from_ ='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
where_['tenant_id'] = tenant_id
where_['uuid'] = flavor_id
result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
if result < 0:
print "http_get_flavor_id error %d %s" % (result, content)
bottle.abort(-result, content)
elif result==0:
print "http_get_flavors_id flavor '%s' not found" % str(flavor_id)
bottle.abort(HTTP_Not_Found, 'flavor %s not found' % flavor_id)
else:
change_keys_http2db(content, http2db_flavor, reverse=True)
if 'extended' in content[0] and content[0]['extended'] is not None:
extended = json.loads(content[0]['extended'])
if 'devices' in extended:
change_keys_http2db(extended['devices'], http2db_flavor, reverse=True)
content[0]['extended']=extended
convert_bandwidth(content[0], reverse=True)
content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
data={'flavor' : content[0]}
#data['tenants_links'] = dict([('tenant', row['id']) for row in content])
return format_out(data)
@bottle.route(url_base + '/<tenant_id>/flavors', method='POST')
def http_post_flavors(tenant_id):
'''insert a flavor into the database, and attach to tenant.'''
my = config_dic['http_threads'][ threading.current_thread().name ]
#check valid tenant_id
result,content = check_valid_tenant(my, tenant_id)
if result != 0:
bottle.abort(result, content)
http_content = format_in( flavor_new_schema )
r = remove_extra_items(http_content, flavor_new_schema)
if r is not None: print "http_post_flavors: Warning: remove extra items ", r
change_keys_http2db(http_content['flavor'], http2db_flavor)
extended_dict = http_content['flavor'].pop('extended', None)
if extended_dict is not None:
result, content = check_extended(extended_dict)
if result<0:
print "http_post_flavors wrong input extended error %d %s" % (result, content)
bottle.abort(-result, content)
return
convert_bandwidth(extended_dict)
if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
http_content['flavor']['extended'] = json.dumps(extended_dict)
#insert in data base
result, content = my.db.new_flavor(http_content['flavor'], tenant_id)
if result >= 0:
return http_get_flavor_id(tenant_id, content)
else:
print "http_psot_flavors error %d %s" % (result, content)
bottle.abort(-result, content)
return
@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='DELETE')
def http_delete_flavor_id(tenant_id, flavor_id):
'''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
my = config_dic['http_threads'][ threading.current_thread().name ]
#check valid tenant_id
result,content = check_valid_tenant(my, tenant_id)
if result != 0:
bottle.abort(result, content)
return
result, content = my.db.delete_image_flavor('flavor', flavor_id, tenant_id)
if result == 0:
bottle.abort(HTTP_Not_Found, content)
elif result >0:
data={'result' : content}
return format_out(data)
else:
print "http_delete_flavor_id error",result, content
bottle.abort(-result, content)
return
@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>/<action>', method='POST')
def http_attach_detach_flavors(tenant_id, flavor_id, action):
'''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
#TODO alf: not tested at all!!!
my = config_dic['http_threads'][ threading.current_thread().name ]
#check valid tenant_id
result,content = check_valid_tenant(my, tenant_id)
if result != 0:
bottle.abort(result, content)
if tenant_id=='any':
bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
#check valid action
if action!='attach' and action != 'detach':
bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
return
#Ensure that flavor exist
from_ ='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
where_={'uuid': flavor_id}
result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
if result==0:
if action=='attach':
text_error="Flavor '%s' not found" % flavor_id
else:
text_error="Flavor '%s' not found for tenant '%s'" % (flavor_id, tenant_id)
bottle.abort(HTTP_Not_Found, text_error)
return
elif result>0:
flavor=content[0]
if action=='attach':
if flavor['tenant_id']!=None:
bottle.abort(HTTP_Conflict, "Flavor '%s' already attached to tenant '%s'" % (flavor_id, tenant_id))
if flavor['public']=='no' and not my.admin:
#allow only attaching public flavors
bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private flavor")
return
#insert in data base
result, content = my.db.new_row('tenants_flavors', {'flavor_id':flavor_id, 'tenant_id': tenant_id})
if result >= 0:
return http_get_flavor_id(tenant_id, flavor_id)
else: #detach
if flavor['tenant_id']==None:
bottle.abort(HTTP_Not_Found, "Flavor '%s' not attached to tenant '%s'" % (flavor_id, tenant_id))
result, content = my.db.delete_row_by_dict(FROM='tenants_flavors', WHERE={'flavor_id':flavor_id, 'tenant_id':tenant_id})
if result>=0:
if flavor['public']=='no':
#try to delete the flavor completely to avoid orphan flavors, IGNORE error
my.db.delete_row_by_dict(FROM='flavors', WHERE={'uuid':flavor_id})
data={'result' : "flavor detached"}
return format_out(data)
#if get here is because an error
print "http_attach_detach_flavors error %d %s" % (result, content)
bottle.abort(-result, content)
return
@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='PUT')
def http_put_flavor_id(tenant_id, flavor_id):
'''update a flavor_id into the database.'''
my = config_dic['http_threads'][ threading.current_thread().name ]
#check valid tenant_id
result,content = check_valid_tenant(my, tenant_id)
if result != 0:
bottle.abort(result, content)
#parse input data
http_content = format_in( flavor_update_schema )
r = remove_extra_items(http_content, flavor_update_schema)
if r is not None: print "http_put_flavor_id: Warning: remove extra items ", r
change_keys_http2db(http_content['flavor'], http2db_flavor)
extended_dict = http_content['flavor'].pop('extended', None)
if extended_dict is not None:
result, content = check_extended(extended_dict)
if result<0:
print "http_put_flavor_id wrong input extended error %d %s" % (result, content)
bottle.abort(-result, content)
return
convert_bandwidth(extended_dict)
if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
http_content['flavor']['extended'] = json.dumps(extended_dict)
#Ensure that flavor exist
where_={'uuid': flavor_id}
if tenant_id=='any':
from_ ='flavors'
else:
from_ ='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
where_['tenant_id'] = tenant_id
result, content = my.db.get_table(SELECT=('public',), FROM=from_, WHERE=where_)
if result==0:
text_error="Flavor '%s' not found" % flavor_id
if tenant_id!='any':
text_error +=" for tenant '%s'" % flavor_id
bottle.abort(HTTP_Not_Found, text_error)
return
elif result>0:
if content[0]['public']=='yes' and not my.admin:
#allow only modifications over private flavors
bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public flavor")
return
#insert in data base
result, content = my.db.update_rows('flavors', http_content['flavor'], {'uuid': flavor_id})
if result < 0:
print "http_put_flavor_id error %d %s" % (result, content)
bottle.abort(-result, content)
return
else:
return http_get_flavor_id(tenant_id, flavor_id)