1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 This is the thread for the http server North API.
26 Two thread will be launched, with normal and administrative permissions.
29 __author__
="Alfonso Tierno, Gerardo Garcia, Leonardo Mirabal"
30 __date__
="$10-jul-2014 12:07:15$"
41 from netaddr
import IPNetwork
, IPAddress
, all_matching_cidrs
42 #import only if needed because not needed in test mode. To allow an easier installation import RADclass
43 from jsonschema
import validate
as js_v
, exceptions
as js_e
44 import host_thread
as ht
45 from vim_schema
import host_new_schema
, host_edit_schema
, tenant_new_schema
, \
47 flavor_new_schema
, flavor_update_schema
, \
48 image_new_schema
, image_update_schema
, \
49 server_new_schema
, server_action_schema
, network_new_schema
, network_update_schema
, \
50 port_new_schema
, port_update_schema
57 global RADclass_module
58 RADclass
=None #RADclass module is charged only if not in test mode
62 HTTP_Bad_Request
= 400
63 HTTP_Unauthorized
= 401
66 HTTP_Method_Not_Allowed
= 405
67 HTTP_Not_Acceptable
= 406
68 HTTP_Request_Timeout
= 408
70 HTTP_Service_Unavailable
= 503
71 HTTP_Internal_Server_Error
= 500
74 hash_md5
= hashlib
.md5()
75 with
open(fname
, "rb") as f
:
76 for chunk
in iter(lambda: f
.read(4096), b
""):
77 hash_md5
.update(chunk
)
78 return hash_md5
.hexdigest()
80 def md5_string(fname
):
81 hash_md5
= hashlib
.md5()
82 hash_md5
.update(fname
)
83 return hash_md5
.hexdigest()
85 def check_extended(extended
, allow_net_attach
=False):
86 '''Makes and extra checking of extended input that cannot be done using jsonschema
88 allow_net_attach: for allowing or not the uuid field at interfaces
89 that are allowed for instance, but not for flavors
90 Return: (<0, error_text) if error; (0,None) if not error '''
91 if "numas" not in extended
: return 0, None
94 for numa
in extended
["numas"]:
98 if "cores-id" in numa
:
99 if len(numa
["cores-id"]) != numa
["cores"]:
100 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
101 id_s
.extend(numa
["cores-id"])
102 if "threads" in numa
:
104 if "threads-id" in numa
:
105 if len(numa
["threads-id"]) != numa
["threads"]:
106 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
107 id_s
.extend(numa
["threads-id"])
108 if "paired-threads" in numa
:
110 if "paired-threads-id" in numa
:
111 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
112 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
)
113 for pair
in numa
["paired-threads-id"]:
115 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
118 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
120 if "interfaces" in numa
:
124 for interface
in numa
["interfaces"]:
125 if "uuid" in interface
and not allow_net_attach
:
126 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
127 if "mac_address" in interface
and interface
["dedicated"]=="yes":
128 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
)
129 if "name" in interface
:
130 if interface
["name"] in names
:
131 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
132 names
.append(interface
["name"])
133 if "vpci" in interface
:
134 if interface
["vpci"] in vpcis
:
135 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
136 vpcis
.append(interface
["vpci"])
140 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
141 for a
in range(0,len(id_s
)):
143 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
148 # dictionaries that change from HTTP API to database naming
150 http2db_host
={'id':'uuid'}
151 http2db_tenant
={'id':'uuid'}
152 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
153 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
154 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
155 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
156 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'}
158 def remove_extra_items(data
, schema
):
160 if type(data
) is tuple or type(data
) is list:
162 a
= remove_extra_items(d
, schema
['items'])
163 if a
is not None: deleted
.append(a
)
164 elif type(data
) is dict:
165 for k
in data
.keys():
166 if 'properties' not in schema
or k
not in schema
['properties'].keys():
170 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
171 if a
is not None: deleted
.append({k
:a
})
172 if len(deleted
) == 0: return None
173 elif len(deleted
) == 1: return deleted
[0]
176 def delete_nulls(var
):
177 if type(var
) is dict:
179 if var
[k
] is None: del var
[k
]
180 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
181 if delete_nulls(var
[k
]): del var
[k
]
182 if len(var
) == 0: return True
183 elif type(var
) is list or type(var
) is tuple:
185 if type(k
) is dict: delete_nulls(k
)
186 if len(var
) == 0: return True
190 class httpserver(threading
.Thread
):
191 def __init__(self
, ovim
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
193 Creates a new thread to attend the http connections
195 db_conn: database connection
196 name: name of this thread
197 host: ip or name where to listen
198 port: port where to listen
199 admin: if this has privileges of administrator or not
200 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
206 if config_
is not None:
208 if 'http_threads' not in config_dic
:
209 config_dic
['http_threads'] = {}
210 threading
.Thread
.__init
__(self
)
213 self
.db
= ovim
.db
#TODO OVIM remove
216 if name
in config_dic
:
217 print "httpserver Warning!!! Onether thread with the same name", name
219 while name
+str(n
) in config_dic
:
223 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
224 config_dic
['http_threads'][name
] = self
226 #Ensure that when the main program exits the thread will also exit
229 self
.logger
= logging
.getLogger("openvim.http")
232 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
234 def gethost(self
, host_id
):
235 result
, content
= self
.db
.get_host(host_id
)
237 print "httpserver.gethost error %d %s" % (result
, content
)
238 bottle
.abort(-result
, content
)
240 print "httpserver.gethost host '%s' not found" % host_id
241 bottle
.abort(HTTP_Not_Found
, content
)
243 data
={'host' : content
}
244 convert_boolean(content
, ('admin_state_up',) )
245 change_keys_http2db(content
, http2db_host
, reverse
=True)
247 return format_out(data
)
249 @bottle.route(url_base
+ '/', method
='GET')
252 return 'works' #TODO: put links or redirection to /openvim???
258 def change_keys_http2db(data
, http_db
, reverse
=False):
259 '''Change keys of dictionary data according to the key_dict values
260 This allow change from http interface names to database names.
261 When reverse is True, the change is otherwise
263 data: can be a dictionary or a list
264 http_db: is a dictionary with hhtp names as keys and database names as value
265 reverse: by default change is done from http API to database. If True change is done otherwise
266 Return: None, but data is modified'''
267 if type(data
) is tuple or type(data
) is list:
269 change_keys_http2db(d
, http_db
, reverse
)
270 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
272 for k
,v
in http_db
.items():
273 if v
in data
: data
[k
]=data
.pop(v
)
275 for k
,v
in http_db
.items():
276 if k
in data
: data
[v
]=data
.pop(k
)
280 def format_out(data
):
281 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
282 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
283 bottle
.response
.content_type
='application/yaml'
284 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='"'
285 else: #by default json
286 bottle
.response
.content_type
='application/json'
287 #return data #json no style
288 return json
.dumps(data
, indent
=4) + "\n"
290 def format_in(schema
):
292 error_text
= "Invalid header format "
293 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
294 if 'application/json' in format_type
:
295 error_text
= "Invalid json format "
296 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
297 client_data
= json
.load(bottle
.request
.body
)
298 #client_data = bottle.request.json()
299 elif 'application/yaml' in format_type
:
300 error_text
= "Invalid yaml format "
301 client_data
= yaml
.load(bottle
.request
.body
)
302 elif format_type
== 'application/xml':
303 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
305 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
306 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
308 #if client_data == None:
309 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
313 #print "HTTP input data: ", str(client_data)
314 error_text
= "Invalid content "
315 js_v(client_data
, schema
)
318 except (ValueError, yaml
.YAMLError
) as exc
:
319 error_text
+= str(exc
)
321 bottle
.abort(HTTP_Bad_Request
, error_text
)
322 except js_e
.ValidationError
as exc
:
323 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
324 print " CONTENT: " + str(bottle
.request
.body
.readlines())
326 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
327 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
329 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
332 def filter_query_string(qs
, http2db
, allowed
):
333 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
335 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
336 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
337 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
338 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
339 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
340 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
341 limit: limit dictated by user with the query string 'limit'. 100 by default
342 abort if not permitted, using bottel.abort
347 if type(qs
) is not bottle
.FormsDict
:
348 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
349 # bottle.abort(HTTP_Internal_Server_Error, "call programmer")
353 select
+= qs
.getall(k
)
356 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field=" + v
+ "'")
361 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit=" + qs
[k
] + "'")
364 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '" + k
+ "=" + qs
[k
] + "'")
369 if len(select
) == 0: select
+= allowed
370 # change from http api to database naming
371 for i
in range(0, len(select
)):
374 select
[i
] = http2db
[k
]
375 change_keys_http2db(where
, http2db
)
376 # print "filter_query_string", select,where,limit
378 return select
, where
, limit
380 def convert_bandwidth(data
, reverse
=False):
381 '''Check the field bandwidth recursively and when found, it removes units and convert to number
382 It assumes that bandwidth is well formed
384 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
385 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
389 if type(data
) is dict:
390 for k
in data
.keys():
391 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
392 convert_bandwidth(data
[k
], reverse
)
393 if "bandwidth" in data
:
395 value
=str(data
["bandwidth"])
397 pos
= value
.find("bps")
399 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
400 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
401 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
403 value
= int(data
["bandwidth"])
404 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
405 else: data
["bandwidth"]=str(value
) + " Mbps"
407 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
409 if type(data
) is tuple or type(data
) is list:
411 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
412 convert_bandwidth(k
, reverse
)
414 def convert_boolean(data
, items
): #TODO OVIM delete
415 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
416 It assumes that bandwidth is well formed
418 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
419 'items': tuple of keys to convert
423 if type(data
) is dict:
424 for k
in data
.keys():
425 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
426 convert_boolean(data
[k
], items
)
428 if type(data
[k
]) is str:
429 if data
[k
]=="false": data
[k
]=False
430 elif data
[k
]=="true": data
[k
]=True
431 if type(data
) is tuple or type(data
) is list:
433 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
434 convert_boolean(k
, items
)
436 def convert_datetime2str(var
):
437 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
438 It enters recursively in the dict var finding this kind of variables
440 if type(var
) is dict:
441 for k
,v
in var
.items():
442 if type(v
) is datetime
.datetime
:
443 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
444 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
445 convert_datetime2str(v
)
446 if len(var
) == 0: return True
447 elif type(var
) is list or type(var
) is tuple:
449 convert_datetime2str(v
)
451 def check_valid_tenant(my
, tenant_id
):
454 return HTTP_Unauthorized
, "Needed admin privileges"
456 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
458 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
461 def check_valid_uuid(uuid
):
462 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
464 js_v(uuid
, id_schema
)
466 except js_e
.ValidationError
:
472 Check if string value is a well-wormed url
473 :param url: string url
474 :return: True if is a valid url, False if is not well-formed
477 parsed_url
= urlparse
.urlparse(url
)
492 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
495 @bottle.hook('after_request')
497 #TODO: Alf: Is it needed??
498 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
504 @bottle.route(url_base
+ '/hosts', method
='GET')
505 def http_get_hosts():
506 return format_out(get_hosts())
510 select_
, where_
, limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
511 ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name'))
513 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
514 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
516 print "http_get_hosts Error", content
517 bottle
.abort(-result
, content
)
519 convert_boolean(content
, ('admin_state_up',) )
520 change_keys_http2db(content
, http2db_host
, reverse
=True)
522 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
523 data
={'hosts' : content
}
526 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
527 def http_get_host_id(host_id
):
528 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
529 return my
.gethost(host_id
)
531 @bottle.route(url_base
+ '/hosts', method
='POST')
532 def http_post_hosts():
533 '''insert a host into the database. All resources are got and inserted'''
534 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
537 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
540 http_content
= format_in( host_new_schema
)
541 r
= remove_extra_items(http_content
, host_new_schema
)
542 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
543 change_keys_http2db(http_content
['host'], http2db_host
)
545 host
= http_content
['host']
547 if 'host-data' in http_content
:
548 host
.update(http_content
['host-data'])
549 ip_name
=http_content
['host-data']['ip_name']
550 user
=http_content
['host-data']['user']
551 password
=http_content
['host-data'].get('password', None)
553 ip_name
=host
['ip_name']
555 password
=host
.get('password', None)
556 if not RADclass_module
:
558 RADclass_module
= imp
.find_module("RADclass")
559 except (IOError, ImportError) as e
:
560 raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e
))
563 rad
= RADclass_module
.RADclass()
564 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
567 if not return_status
:
568 print 'http_post_hosts ERROR obtaining RAD', code
569 bottle
.abort(HTTP_Bad_Request
, code
)
572 rad_structure
= yaml
.load(rad
.to_text())
573 print 'rad_structure\n---------------------'
574 print json
.dumps(rad_structure
, indent
=4)
575 print '---------------------'
577 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
578 result
, content
= my
.db
.get_table(FROM
='host_ranking',
582 host
['ranking'] = content
[0]['ranking']
584 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
585 #bottle.abort(HTTP_Bad_Request, error_text)
587 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
588 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
590 features
= rad_structure
['processor'].get('features', ())
591 host
['features'] = ",".join(features
)
594 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
599 for core
in node
['cpu']['eligible_cores']:
600 eligible_cores
.extend(core
)
601 for core
in node
['cpu']['cores']:
602 for thread_id
in core
:
603 c
={'core_id': count
, 'thread_id': thread_id
}
604 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
609 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
610 if port_v
['virtual']:
614 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
615 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
616 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
618 #sort sriov according to pci and rename them to the vf number
619 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
621 for sriov
in new_sriovs
:
622 sriov
['source_name'] = index
624 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
625 memory
=node
['memory']['node_size'] / (1024*1024*1024)
626 #memory=get_next_2pow(node['memory']['hugepage_nr'])
627 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
628 print json
.dumps(host
, indent
=4)
632 result
, content
= my
.db
.new_host(host
)
634 if content
['admin_state_up']:
636 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
637 host_develop_mode
= True if config_dic
['mode']=='development' else False
638 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
639 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'],
640 test
=host_test_mode
, image_path
=config_dic
['image_path'],
641 version
=config_dic
['version'], host_id
=content
['uuid'],
642 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
644 config_dic
['host_threads'][ content
['uuid'] ] = thread
646 if config_dic
['network_type'] == 'ovs':
648 create_dhcp_ovs_bridge()
649 config_dic
['host_threads'][content
['uuid']].insert_task("new-ovsbridge")
650 # check if more host exist
651 create_vxlan_mesh(content
['uuid'])
654 change_keys_http2db(content
, http2db_host
, reverse
=True)
655 if len(warning_text
)>0:
656 content
["warning"]= warning_text
657 data
={'host' : content
}
658 return format_out(data
)
660 bottle
.abort(HTTP_Bad_Request
, content
)
664 def delete_dhcp_ovs_bridge(vlan
, net_uuid
):
666 Delete bridges and port created during dhcp launching at openvim controller
667 :param vlan: net vlan id
668 :param net_uuid: network identifier
671 dhcp_path
= config_dic
['ovs_controller_file_path']
673 http_controller
= config_dic
['http_threads'][threading
.current_thread().name
]
674 dhcp_controller
= http_controller
.ovim
.get_dhcp_controller()
676 dhcp_controller
.delete_dhcp_port(vlan
, net_uuid
)
677 dhcp_controller
.delete_dhcp_server(vlan
, net_uuid
, dhcp_path
)
680 def create_dhcp_ovs_bridge():
682 Initialize bridge to allocate the dhcp server at openvim controller
685 http_controller
= config_dic
['http_threads'][threading
.current_thread().name
]
686 dhcp_controller
= http_controller
.ovim
.get_dhcp_controller()
688 dhcp_controller
.create_ovs_bridge()
691 def set_mac_dhcp(vm_ip
, vlan
, first_ip
, last_ip
, cidr
, mac
):
693 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
694 :param vm_ip: IP address asigned to a VM
695 :param vlan: Segmentation id
696 :param first_ip: First dhcp range ip
697 :param last_ip: Last dhcp range ip
698 :param cidr: net cidr
699 :param mac: VM vnic mac to be macthed with the IP received
703 ip_tools
= IPNetwork(cidr
)
704 cidr_len
= ip_tools
.prefixlen
705 dhcp_netmask
= str(ip_tools
.netmask
)
706 dhcp_path
= config_dic
['ovs_controller_file_path']
708 new_cidr
= [first_ip
+ '/' + str(cidr_len
)]
709 if not len(all_matching_cidrs(vm_ip
, new_cidr
)):
712 http_controller
= config_dic
['http_threads'][threading
.current_thread().name
]
713 dhcp_controller
= http_controller
.ovim
.get_dhcp_controller()
715 dhcp_controller
.set_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_netmask
, dhcp_path
)
718 def delete_mac_dhcp(vm_ip
, vlan
, mac
):
720 Delete into dhcp conf file the ip assigned to a specific MAC address
721 :param vm_ip: IP address asigned to a VM
722 :param vlan: Segmentation id
723 :param mac: VM vnic mac to be macthed with the IP received
727 dhcp_path
= config_dic
['ovs_controller_file_path']
729 http_controller
= config_dic
['http_threads'][threading
.current_thread().name
]
730 dhcp_controller
= http_controller
.ovim
.get_dhcp_controller()
732 dhcp_controller
.delete_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_path
)
735 def create_vxlan_mesh(host_id
):
737 Create vxlan mesh across all openvimc controller and computes.
738 :param host_id: host identifier
739 :param host_id: host identifier
742 dhcp_compute_name
= get_vxlan_interface("dhcp")
743 existing_hosts
= get_hosts()
744 if len(existing_hosts
['hosts']) > 0:
745 # vlxan mesh creation between openvim controller and computes
746 computes_available
= existing_hosts
['hosts']
748 http_controller
= config_dic
['http_threads'][threading
.current_thread().name
]
749 dhcp_controller
= http_controller
.ovim
.get_dhcp_controller()
751 for compute
in computes_available
:
752 vxlan_interface_name
= get_vxlan_interface(compute
['id'][:8])
753 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan", dhcp_compute_name
, dhcp_controller
.host
)
754 dhcp_controller
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute
['ip_name'])
756 # vlxan mesh creation between openvim computes
757 for count
, compute_owner
in enumerate(computes_available
):
758 for compute
in computes_available
:
759 if compute_owner
['id'] == compute
['id']:
762 vxlan_interface_name
= get_vxlan_interface(compute_owner
['id'][:8])
763 dhcp_controller
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute_owner
['ip_name'])
764 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan",
765 vxlan_interface_name
,
766 compute_owner
['ip_name'])
769 def delete_vxlan_mesh(host_id
):
771 Create a task for remove a specific compute of the vlxan mesh
772 :param host_id: host id to be deleted.
774 existing_hosts
= get_hosts()
775 computes_available
= existing_hosts
['hosts']
777 vxlan_interface_name
= get_vxlan_interface(host_id
[:8])
779 http_controller
= config_dic
['http_threads'][threading
.current_thread().name
]
780 dhcp_host
= http_controller
.ovim
.get_dhcp_controller()
782 dhcp_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
783 # remove bridge from openvim controller if no more computes exist
784 if len(existing_hosts
):
785 dhcp_host
.delete_ovs_bridge()
787 for compute
in computes_available
:
788 if host_id
== compute
['id']:
791 dhcp_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
792 config_dic
['host_threads'][compute
['id']].insert_task("del-vxlan", vxlan_interface_name
)
795 def get_vxlan_interface(local_uuid
):
797 Genearte a vxlan interface name
798 :param local_uuid: host id
799 :return: vlxan-8digits
801 return 'vxlan-' + local_uuid
[:8]
804 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
805 def http_put_host_id(host_id
):
806 '''modify a host into the database. All resources are got and inserted'''
807 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
810 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
813 http_content
= format_in( host_edit_schema
)
814 r
= remove_extra_items(http_content
, host_edit_schema
)
815 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
816 change_keys_http2db(http_content
['host'], http2db_host
)
819 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
821 convert_boolean(content
, ('admin_state_up',) )
822 change_keys_http2db(content
, http2db_host
, reverse
=True)
823 data
={'host' : content
}
825 if config_dic
['network_type'] == 'ovs':
826 delete_vxlan_mesh(host_id
)
827 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
830 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
831 config_dic
['host_threads'][host_id
].user
= content
['user']
832 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
833 config_dic
['host_threads'][host_id
].insert_task("reload")
835 if config_dic
['network_type'] == 'ovs':
836 # create mesh with new host data
837 config_dic
['host_threads'][host_id
].insert_task("new-ovsbridge")
838 create_vxlan_mesh(host_id
)
841 return format_out(data
)
843 bottle
.abort(HTTP_Bad_Request
, content
)
848 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
849 def http_delete_host_id(host_id
):
850 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
853 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
854 result
, content
= my
.db
.delete_row('hosts', host_id
)
856 bottle
.abort(HTTP_Not_Found
, content
)
858 if config_dic
['network_type'] == 'ovs':
859 delete_vxlan_mesh(host_id
)
861 if host_id
in config_dic
['host_threads']:
862 if config_dic
['network_type'] == 'ovs':
863 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
864 config_dic
['host_threads'][host_id
].insert_task("exit")
866 data
={'result' : content
}
867 return format_out(data
)
869 print "http_delete_host_id error",result
, content
870 bottle
.abort(-result
, content
)
879 @bottle.route(url_base
+ '/tenants', method
='GET')
880 def http_get_tenants():
881 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
882 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
883 ('id','name','description','enabled') )
884 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
886 print "http_get_tenants Error", content
887 bottle
.abort(-result
, content
)
889 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
890 convert_boolean(content
, ('enabled',))
891 data
={'tenants' : content
}
892 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
893 return format_out(data
)
895 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
896 def http_get_tenant_id(tenant_id
):
897 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
898 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
900 print "http_get_tenant_id error %d %s" % (result
, content
)
901 bottle
.abort(-result
, content
)
903 print "http_get_tenant_id tenant '%s' not found" % tenant_id
904 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
906 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
907 convert_boolean(content
, ('enabled',))
908 data
={'tenant' : content
[0]}
909 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
910 return format_out(data
)
913 @bottle.route(url_base
+ '/tenants', method
='POST')
914 def http_post_tenants():
915 '''insert a tenant into the database.'''
916 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
918 http_content
= format_in( tenant_new_schema
)
919 r
= remove_extra_items(http_content
, tenant_new_schema
)
920 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
921 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
924 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
927 return http_get_tenant_id(content
)
929 bottle
.abort(-result
, content
)
932 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
933 def http_put_tenant_id(tenant_id
):
934 '''update a tenant into the database.'''
935 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
937 http_content
= format_in( tenant_edit_schema
)
938 r
= remove_extra_items(http_content
, tenant_edit_schema
)
939 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
940 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
943 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
945 return http_get_tenant_id(tenant_id
)
947 bottle
.abort(-result
, content
)
950 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
951 def http_delete_tenant_id(tenant_id
):
952 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
954 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
957 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
960 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
962 bottle
.abort(HTTP_Not_Found
, content
)
964 print "alf", tenants_flavors
, tenants_images
965 for flavor
in tenants_flavors
:
966 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
967 for image
in tenants_images
:
968 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
969 data
={'result' : content
}
970 return format_out(data
)
972 print "http_delete_tenant_id error",result
, content
973 bottle
.abort(-result
, content
)
980 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
981 def http_get_flavors(tenant_id
):
982 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
983 #check valid tenant_id
984 result
,content
= check_valid_tenant(my
, tenant_id
)
986 bottle
.abort(result
, content
)
988 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
989 ('id','name','description','public') )
993 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
994 where_
['tenant_id'] = tenant_id
995 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
997 print "http_get_flavors Error", content
998 bottle
.abort(-result
, content
)
1000 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1002 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
1003 data
={'flavors' : content
}
1004 return format_out(data
)
1006 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
1007 def http_get_flavor_id(tenant_id
, flavor_id
):
1008 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1009 #check valid tenant_id
1010 result
,content
= check_valid_tenant(my
, tenant_id
)
1012 bottle
.abort(result
, content
)
1014 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1015 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1016 if tenant_id
=='any':
1019 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1020 where_
['tenant_id'] = tenant_id
1021 where_
['uuid'] = flavor_id
1022 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1025 print "http_get_flavor_id error %d %s" % (result
, content
)
1026 bottle
.abort(-result
, content
)
1028 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
1029 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
1031 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1032 if 'extended' in content
[0] and content
[0]['extended'] is not None:
1033 extended
= json
.loads(content
[0]['extended'])
1034 if 'devices' in extended
:
1035 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
1036 content
[0]['extended']=extended
1037 convert_bandwidth(content
[0], reverse
=True)
1038 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1039 data
={'flavor' : content
[0]}
1040 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1041 return format_out(data
)
1044 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
1045 def http_post_flavors(tenant_id
):
1046 '''insert a flavor into the database, and attach to tenant.'''
1047 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1048 #check valid tenant_id
1049 result
,content
= check_valid_tenant(my
, tenant_id
)
1051 bottle
.abort(result
, content
)
1052 http_content
= format_in( flavor_new_schema
)
1053 r
= remove_extra_items(http_content
, flavor_new_schema
)
1054 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
1055 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1056 extended_dict
= http_content
['flavor'].pop('extended', None)
1057 if extended_dict
is not None:
1058 result
, content
= check_extended(extended_dict
)
1060 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
1061 bottle
.abort(-result
, content
)
1063 convert_bandwidth(extended_dict
)
1064 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1065 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1066 #insert in data base
1067 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
1069 return http_get_flavor_id(tenant_id
, content
)
1071 print "http_psot_flavors error %d %s" % (result
, content
)
1072 bottle
.abort(-result
, content
)
1075 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
1076 def http_delete_flavor_id(tenant_id
, flavor_id
):
1077 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1078 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1079 #check valid tenant_id
1080 result
,content
= check_valid_tenant(my
, tenant_id
)
1082 bottle
.abort(result
, content
)
1084 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
1086 bottle
.abort(HTTP_Not_Found
, content
)
1088 data
={'result' : content
}
1089 return format_out(data
)
1091 print "http_delete_flavor_id error",result
, content
1092 bottle
.abort(-result
, content
)
1095 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
1096 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
1097 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1098 #TODO alf: not tested at all!!!
1099 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1100 #check valid tenant_id
1101 result
,content
= check_valid_tenant(my
, tenant_id
)
1103 bottle
.abort(result
, content
)
1104 if tenant_id
=='any':
1105 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1107 if action
!='attach' and action
!= 'detach':
1108 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1111 #Ensure that flavor exist
1112 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1113 where_
={'uuid': flavor_id
}
1114 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1116 if action
=='attach':
1117 text_error
="Flavor '%s' not found" % flavor_id
1119 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
1120 bottle
.abort(HTTP_Not_Found
, text_error
)
1124 if action
=='attach':
1125 if flavor
['tenant_id']!=None:
1126 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
1127 if flavor
['public']=='no' and not my
.admin
:
1128 #allow only attaching public flavors
1129 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
1131 #insert in data base
1132 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
1134 return http_get_flavor_id(tenant_id
, flavor_id
)
1136 if flavor
['tenant_id']==None:
1137 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
1138 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
1140 if flavor
['public']=='no':
1141 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1142 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
1143 data
={'result' : "flavor detached"}
1144 return format_out(data
)
1146 #if get here is because an error
1147 print "http_attach_detach_flavors error %d %s" % (result
, content
)
1148 bottle
.abort(-result
, content
)
1151 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
1152 def http_put_flavor_id(tenant_id
, flavor_id
):
1153 '''update a flavor_id into the database.'''
1154 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1155 #check valid tenant_id
1156 result
,content
= check_valid_tenant(my
, tenant_id
)
1158 bottle
.abort(result
, content
)
1160 http_content
= format_in( flavor_update_schema
)
1161 r
= remove_extra_items(http_content
, flavor_update_schema
)
1162 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1163 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1164 extended_dict
= http_content
['flavor'].pop('extended', None)
1165 if extended_dict
is not None:
1166 result
, content
= check_extended(extended_dict
)
1168 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
1169 bottle
.abort(-result
, content
)
1171 convert_bandwidth(extended_dict
)
1172 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1173 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1174 #Ensure that flavor exist
1175 where_
={'uuid': flavor_id
}
1176 if tenant_id
=='any':
1179 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1180 where_
['tenant_id'] = tenant_id
1181 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1183 text_error
="Flavor '%s' not found" % flavor_id
1184 if tenant_id
!='any':
1185 text_error
+=" for tenant '%s'" % flavor_id
1186 bottle
.abort(HTTP_Not_Found
, text_error
)
1189 if content
[0]['public']=='yes' and not my
.admin
:
1190 #allow only modifications over private flavors
1191 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1193 #insert in data base
1194 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1197 print "http_put_flavor_id error %d %s" % (result
, content
)
1198 bottle
.abort(-result
, content
)
1201 return http_get_flavor_id(tenant_id
, flavor_id
)
1209 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1210 def http_get_images(tenant_id
):
1211 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1212 #check valid tenant_id
1213 result
,content
= check_valid_tenant(my
, tenant_id
)
1215 bottle
.abort(result
, content
)
1217 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1218 ('id','name','checksum','description','path','public') )
1219 if tenant_id
=='any':
1223 from_
='tenants_images right join images on tenants_images.image_id=images.uuid'
1224 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1225 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1227 print "http_get_images Error", content
1228 bottle
.abort(-result
, content
)
1230 change_keys_http2db(content
, http2db_image
, reverse
=True)
1231 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1232 data
={'images' : content
}
1233 return format_out(data
)
1235 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1236 def http_get_image_id(tenant_id
, image_id
):
1237 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1238 #check valid tenant_id
1239 result
,content
= check_valid_tenant(my
, tenant_id
)
1241 bottle
.abort(result
, content
)
1243 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1244 ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
1245 if tenant_id
=='any':
1249 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1250 where_or_
= {'tenant_id': tenant_id
, 'public': "yes"}
1251 where_
['uuid'] = image_id
1252 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1255 print "http_get_images error %d %s" % (result
, content
)
1256 bottle
.abort(-result
, content
)
1258 print "http_get_images image '%s' not found" % str(image_id
)
1259 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1261 convert_datetime2str(content
)
1262 change_keys_http2db(content
, http2db_image
, reverse
=True)
1263 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1264 metadata
= json
.loads(content
[0]['metadata'])
1265 content
[0]['metadata']=metadata
1266 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1267 data
={'image' : content
[0]}
1268 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1269 return format_out(data
)
1271 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1272 def http_post_images(tenant_id
):
1273 '''insert a image into the database, and attach to tenant.'''
1274 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1275 #check valid tenant_id
1276 result
,content
= check_valid_tenant(my
, tenant_id
)
1278 bottle
.abort(result
, content
)
1279 http_content
= format_in(image_new_schema
)
1280 r
= remove_extra_items(http_content
, image_new_schema
)
1281 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1282 change_keys_http2db(http_content
['image'], http2db_image
)
1283 metadata_dict
= http_content
['image'].pop('metadata', None)
1284 if metadata_dict
is not None:
1285 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1288 image_file
= http_content
['image'].get('path',None)
1289 parsed_url
= urlparse
.urlparse(image_file
)
1290 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "":
1291 # The path is a local file
1292 if os
.path
.exists(image_file
):
1293 http_content
['image']['checksum'] = md5(image_file
)
1295 # The path is a URL. Code should be added to download the image and calculate the checksum
1296 #http_content['image']['checksum'] = md5(downloaded_image)
1298 # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
1299 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1301 if 'checksum' not in http_content
['image']:
1302 http_content
['image']['checksum'] = md5_string(image_file
)
1304 # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
1305 # If it is a URL, no error is sent. Checksum will be an empty string
1306 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "" and 'checksum' not in http_content
['image']:
1307 content
= "Image file not found"
1308 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1309 bottle
.abort(HTTP_Bad_Request
, content
)
1310 except Exception as e
:
1311 print "ERROR. Unexpected exception: %s" % (str(e
))
1312 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1313 #insert in data base
1314 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1316 return http_get_image_id(tenant_id
, content
)
1318 print "http_post_images error %d %s" % (result
, content
)
1319 bottle
.abort(-result
, content
)
1322 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1323 def http_delete_image_id(tenant_id
, image_id
):
1324 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1325 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1326 #check valid tenant_id
1327 result
,content
= check_valid_tenant(my
, tenant_id
)
1329 bottle
.abort(result
, content
)
1330 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1332 bottle
.abort(HTTP_Not_Found
, content
)
1334 data
={'result' : content
}
1335 return format_out(data
)
1337 print "http_delete_image_id error",result
, content
1338 bottle
.abort(-result
, content
)
1341 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1342 def http_attach_detach_images(tenant_id
, image_id
, action
):
1343 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1344 #TODO alf: not tested at all!!!
1345 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1346 #check valid tenant_id
1347 result
,content
= check_valid_tenant(my
, tenant_id
)
1349 bottle
.abort(result
, content
)
1350 if tenant_id
=='any':
1351 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1353 if action
!='attach' and action
!= 'detach':
1354 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1357 #Ensure that image exist
1358 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1359 where_
={'uuid': image_id
}
1360 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1362 if action
=='attach':
1363 text_error
="Image '%s' not found" % image_id
1365 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1366 bottle
.abort(HTTP_Not_Found
, text_error
)
1370 if action
=='attach':
1371 if image
['tenant_id']!=None:
1372 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1373 if image
['public']=='no' and not my
.admin
:
1374 #allow only attaching public images
1375 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1377 #insert in data base
1378 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1380 return http_get_image_id(tenant_id
, image_id
)
1382 if image
['tenant_id']==None:
1383 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1384 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1386 if image
['public']=='no':
1387 #try to delete the image completely to avoid orphan images, IGNORE error
1388 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1389 data
={'result' : "image detached"}
1390 return format_out(data
)
1392 #if get here is because an error
1393 print "http_attach_detach_images error %d %s" % (result
, content
)
1394 bottle
.abort(-result
, content
)
1397 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1398 def http_put_image_id(tenant_id
, image_id
):
1399 '''update a image_id into the database.'''
1400 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1401 #check valid tenant_id
1402 result
,content
= check_valid_tenant(my
, tenant_id
)
1404 bottle
.abort(result
, content
)
1406 http_content
= format_in( image_update_schema
)
1407 r
= remove_extra_items(http_content
, image_update_schema
)
1408 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1409 change_keys_http2db(http_content
['image'], http2db_image
)
1410 metadata_dict
= http_content
['image'].pop('metadata', None)
1411 if metadata_dict
is not None:
1412 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1413 #Ensure that image exist
1414 where_
={'uuid': image_id
}
1415 if tenant_id
=='any':
1419 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1420 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1421 result
, content
= my
.db
.get_table(SELECT
=('public',), DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND")
1423 text_error
="Image '%s' not found" % image_id
1424 if tenant_id
!='any':
1425 text_error
+=" for tenant '%s'" % image_id
1426 bottle
.abort(HTTP_Not_Found
, text_error
)
1429 if content
[0]['public']=='yes' and not my
.admin
:
1430 #allow only modifications over private images
1431 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1433 #insert in data base
1434 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1437 print "http_put_image_id error %d %s" % (result
, content
)
1438 bottle
.abort(-result
, content
)
1441 return http_get_image_id(tenant_id
, image_id
)
1448 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1449 def http_get_servers(tenant_id
):
1450 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1451 result
,content
= check_valid_tenant(my
, tenant_id
)
1453 bottle
.abort(result
, content
)
1456 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1457 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1458 if tenant_id
!='any':
1459 where_
['tenant_id'] = tenant_id
1460 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1462 print "http_get_servers Error", content
1463 bottle
.abort(-result
, content
)
1465 change_keys_http2db(content
, http2db_server
, reverse
=True)
1467 tenant_id
= row
.pop('tenant_id')
1468 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1469 data
={'servers' : content
}
1470 return format_out(data
)
1472 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1473 def http_get_server_id(tenant_id
, server_id
):
1474 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1475 #check valid tenant_id
1476 result
,content
= check_valid_tenant(my
, tenant_id
)
1478 bottle
.abort(result
, content
)
1481 result
, content
= my
.db
.get_instance(server_id
)
1483 bottle
.abort(HTTP_Not_Found
, content
)
1485 #change image/flavor-id to id and link
1486 convert_bandwidth(content
, reverse
=True)
1487 convert_datetime2str(content
)
1488 if content
["ram"]==0 : del content
["ram"]
1489 if content
["vcpus"]==0 : del content
["vcpus"]
1490 if 'flavor_id' in content
:
1491 if content
['flavor_id'] is not None:
1492 content
['flavor'] = {'id':content
['flavor_id'],
1493 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1495 del content
['flavor_id']
1496 if 'image_id' in content
:
1497 if content
['image_id'] is not None:
1498 content
['image'] = {'id':content
['image_id'],
1499 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1501 del content
['image_id']
1502 change_keys_http2db(content
, http2db_server
, reverse
=True)
1503 if 'extended' in content
:
1504 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1506 data
={'server' : content
}
1507 return format_out(data
)
1509 bottle
.abort(-result
, content
)
1512 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1513 def http_post_server_id(tenant_id
):
1514 '''deploys a new server'''
1515 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1516 #check valid tenant_id
1517 result
,content
= check_valid_tenant(my
, tenant_id
)
1519 bottle
.abort(result
, content
)
1521 if tenant_id
=='any':
1522 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1524 http_content
= format_in( server_new_schema
)
1525 r
= remove_extra_items(http_content
, server_new_schema
)
1526 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1527 change_keys_http2db(http_content
['server'], http2db_server
)
1528 extended_dict
= http_content
['server'].get('extended', None)
1529 if extended_dict
is not None:
1530 result
, content
= check_extended(extended_dict
, True)
1532 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1533 bottle
.abort(-result
, content
)
1535 convert_bandwidth(extended_dict
)
1536 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1538 server
= http_content
['server']
1539 server_start
= server
.get('start', 'yes')
1540 server
['tenant_id'] = tenant_id
1541 #check flavor valid and take info
1542 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1543 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1545 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1547 server
['flavor']=content
[0]
1548 #check image valid and take info
1549 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1550 SELECT
=('path', 'metadata', 'image_id'),
1551 WHERE
={'uuid':server
['image_id'], "status":"ACTIVE"},
1552 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'},
1556 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1558 for image_dict
in content
:
1559 if image_dict
.get("image_id"):
1562 # insert in data base tenants_images
1563 r2
, c2
= my
.db
.new_row('tenants_images', {'image_id': server
['image_id'], 'tenant_id': tenant_id
})
1565 bottle
.abort(HTTP_Not_Found
, 'image_id %s cannot be used. Error %s' % (server
['image_id'], c2
))
1567 server
['image']={"path": content
[0]["path"], "metadata": content
[0]["metadata"]}
1568 if "hosts_id" in server
:
1569 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1571 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1573 #print json.dumps(server, indent=4)
1575 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1578 #Insert instance to database
1581 print "inserting at DB"
1583 if server_start
== 'no':
1584 content
['status'] = 'INACTIVE'
1586 for net
in http_content
['server']['networks']:
1587 if net
['type'] == 'instance:ovs':
1588 dhcp_nets_id
.append(get_network_id(net
['net_id']))
1591 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1592 if new_instance_result
< 0:
1593 print "Error http_post_servers() :", new_instance_result
, new_instance
1594 bottle
.abort(-new_instance_result
, new_instance
)
1597 print "inserted at DB"
1599 for port
in ports_to_free
:
1600 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1602 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1605 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1607 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1610 #look for dhcp ip address
1611 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "ip_address", "net_id"], WHERE
={"instance_id": new_instance
})
1614 if config_dic
.get("dhcp_server") and iface
["net_id"] in config_dic
["dhcp_nets"]:
1615 #print "dhcp insert add task"
1616 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1618 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1620 #ensure compute contain the bridge for ovs networks:
1621 server_net
= get_network_id(iface
['net_id'])
1622 if server_net
["network"].get('provider:physical', "")[:3] == 'OVS':
1623 vlan
= str(server_net
['network']['provider:vlan'])
1624 dhcp_enable
= bool(server_net
['network']['enable_dhcp'])
1626 dhcp_firt_ip
= str(server_net
['network']['dhcp_first_ip'])
1627 dhcp_last_ip
= str(server_net
['network']['dhcp_last_ip'])
1628 dhcp_cidr
= str(server_net
['network']['cidr'])
1629 vm_dhcp_ip
= c2
[0]["ip_address"]
1630 config_dic
['host_threads'][server
['host_id']].insert_task("create-ovs-bridge-port", vlan
)
1632 set_mac_dhcp(vm_dhcp_ip
, vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
, c2
[0]['mac'])
1633 http_controller
= config_dic
['http_threads'][threading
.current_thread().name
]
1634 http_controller
.ovim
.launch_dhcp_server(vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
)
1637 server
['uuid'] = new_instance
1638 server_start
= server
.get('start', 'yes')
1640 if server_start
!= 'no':
1641 server
['paused'] = True if server_start
== 'paused' else False
1642 server
['action'] = {"start":None}
1643 server
['status'] = "CREATING"
1645 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1647 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1649 return http_get_server_id(tenant_id
, new_instance
)
1651 bottle
.abort(HTTP_Bad_Request
, content
)
1654 def http_server_action(server_id
, tenant_id
, action
):
1655 '''Perform actions over a server as resume, reboot, terminate, ...'''
1656 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1657 server
={"uuid": server_id
, "action":action
}
1658 where
={'uuid': server_id
}
1659 if tenant_id
!='any':
1660 where
['tenant_id']= tenant_id
1661 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1663 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1666 print "http_post_server_action error getting data %d %s" % (result
, content
)
1667 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1669 server
.update(content
[0])
1670 tenant_id
= server
["tenant_id"]
1672 #TODO check a right content
1674 if 'terminate' in action
:
1675 new_status
='DELETING'
1676 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1677 if 'terminate' not in action
and 'rebuild' not in action
:
1678 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1680 # elif server['status'] == 'INACTIVE':
1681 # if 'start' not in action and 'createImage' not in action:
1682 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1684 # if 'start' in action:
1685 # new_status='CREATING'
1686 # server['paused']='no'
1687 # elif server['status'] == 'PAUSED':
1688 # if 'resume' not in action:
1689 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1691 # elif server['status'] == 'ACTIVE':
1692 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1693 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1696 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1697 #check image valid and take info
1698 image_id
= server
['image_id']
1699 if 'createImage' in action
:
1700 if 'imageRef' in action
['createImage']:
1701 image_id
= action
['createImage']['imageRef']
1702 elif 'disk' in action
['createImage']:
1703 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1704 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1706 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1710 if action
['createImage']['imageRef']['disk'] != None:
1711 for disk
in content
:
1712 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1713 disk_id
= disk
['image_id']
1716 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1719 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1723 image_id
= content
[0]['image_id']
1725 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1726 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, "status":"ACTIVE"},
1727 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'}, WHERE_AND_OR
="AND", DISTINCT
=True)
1729 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1731 if content
[0]['metadata'] is not None:
1733 metadata
= json
.loads(content
[0]['metadata'])
1735 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1736 content
[0]['metadata']=metadata
1738 content
[0]['metadata'] = {}
1739 server
['image']=content
[0]
1740 if 'createImage' in action
:
1741 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1742 if 'createImage' in action
:
1743 #Create an entry in Database for the new image
1744 new_image
={'status':'BUILD', 'progress': 0 }
1745 new_image_metadata
=content
[0]
1746 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1747 new_image_metadata
.update(server
['image']['metadata'])
1748 new_image_metadata
= {"use_incremental":"no"}
1749 if 'metadata' in action
['createImage']:
1750 new_image_metadata
.update(action
['createImage']['metadata'])
1751 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1752 new_image
['name'] = action
['createImage'].get('name', None)
1753 new_image
['description'] = action
['createImage'].get('description', None)
1754 new_image
['uuid']=my
.db
.new_uuid()
1755 if 'path' in action
['createImage']:
1756 new_image
['path'] = action
['createImage']['path']
1758 new_image
['path']="/provisional/path/" + new_image
['uuid']
1759 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1761 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1763 server
['new_image'] = new_image
1767 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1769 print "Task queue full at host ", server
['host_id']
1770 bottle
.abort(HTTP_Request_Timeout
, c
)
1771 if 'createImage' in action
and result
>= 0:
1772 return http_get_image_id(tenant_id
, image_uuid
)
1774 #Update DB only for CREATING or DELETING status
1775 data
={'result' : 'in process'}
1776 if new_status
!= None and new_status
== 'DELETING':
1781 #look for dhcp ip address
1782 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1783 r
, c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, net_ovs_list
, "requested by http")
1784 for port
in ports_to_free
:
1785 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1787 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1788 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1790 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1792 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1793 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1794 #look for dhcp ip address
1795 if r2
>0 and config_dic
.get("dhcp_server"):
1797 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1798 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1799 #print "dhcp insert del task"
1801 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1802 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1803 for net
in net_ovs_list
:
1808 delete_dhcp_ovs_bridge(vlan
, net_id
)
1809 delete_mac_dhcp(vm_ip
, vlan
, mac
)
1810 config_dic
['host_threads'][server
['host_id']].insert_task('del-ovs-port', vlan
, net_id
)
1811 return format_out(data
)
1815 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1816 def http_delete_server_id(tenant_id
, server_id
):
1817 '''delete a server'''
1818 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1819 #check valid tenant_id
1820 result
,content
= check_valid_tenant(my
, tenant_id
)
1822 bottle
.abort(result
, content
)
1825 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1828 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1829 def http_post_server_action(tenant_id
, server_id
):
1830 '''take an action over a server'''
1831 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1832 #check valid tenant_id
1833 result
,content
= check_valid_tenant(my
, tenant_id
)
1835 bottle
.abort(result
, content
)
1837 http_content
= format_in( server_action_schema
)
1838 #r = remove_extra_items(http_content, server_action_schema)
1839 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1841 return http_server_action(server_id
, tenant_id
, http_content
)
1848 @bottle.route(url_base
+ '/networks', method
='GET')
1849 def http_get_networks():
1850 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1852 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1853 ('id','name','tenant_id','type',
1854 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1855 #TODO temporally remove tenant_id
1856 if "tenant_id" in where_
:
1857 del where_
["tenant_id"]
1858 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1860 print "http_get_networks error %d %s" % (result
, content
)
1861 bottle
.abort(-result
, content
)
1863 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1864 delete_nulls(content
)
1865 change_keys_http2db(content
, http2db_network
, reverse
=True)
1866 data
={'networks' : content
}
1867 return format_out(data
)
1869 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1870 def http_get_network_id(network_id
):
1871 data
= get_network_id(network_id
)
1872 return format_out(data
)
1874 def get_network_id(network_id
):
1875 my
= config_dic
['http_threads'][threading
.current_thread().name
]
1877 where_
= bottle
.request
.query
1878 where_
['uuid'] = network_id
1879 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1882 print "http_get_networks_id error %d %s" % (result
, content
)
1883 bottle
.abort(-result
, content
)
1885 print "http_get_networks_id network '%s' not found" % network_id
1886 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1888 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1889 change_keys_http2db(content
, http2db_network
, reverse
=True)
1891 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1892 WHERE
={'net_id': network_id
}, LIMIT
=100)
1894 content
[0]['ports'] = ports
1895 delete_nulls(content
[0])
1896 data
={'network' : content
[0]}
1899 @bottle.route(url_base
+ '/networks', method
='POST')
1900 def http_post_networks():
1901 '''insert a network into the database.'''
1902 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1904 http_content
= format_in( network_new_schema
)
1905 r
= remove_extra_items(http_content
, network_new_schema
)
1906 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1907 change_keys_http2db(http_content
['network'], http2db_network
)
1908 network
=http_content
['network']
1909 #check valid tenant_id
1910 tenant_id
= network
.get('tenant_id')
1912 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1914 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1918 net_provider
= network
.get('provider')
1919 net_type
= network
.get('type')
1920 net_enable_dhcp
= network
.get('enable_dhcp')
1922 net_cidr
= network
.get('cidr')
1924 net_vlan
= network
.get("vlan")
1925 net_bind_net
= network
.get("bind_net")
1926 net_bind_type
= network
.get("bind_type")
1927 name
= network
["name"]
1929 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1930 vlan_index
=name
.rfind(":")
1931 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1933 vlan_tag
= int(name
[vlan_index
+1:])
1934 if vlan_tag
>0 and vlan_tag
< 4096:
1935 net_bind_net
= name
[:vlan_index
]
1936 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1940 if net_bind_net
!= None:
1941 #look for a valid net
1942 if check_valid_uuid(net_bind_net
):
1943 net_bind_key
= "uuid"
1945 net_bind_key
= "name"
1946 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1948 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1951 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1954 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1956 network
["bind_net"] = content
[0]["uuid"]
1957 if net_bind_type
!= None:
1958 if net_bind_type
[0:5] != "vlan:":
1959 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1961 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1962 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1964 network
["bind_type"] = net_bind_type
1966 if net_provider
!=None:
1967 if net_provider
[:9]=="openflow:":
1969 if net_type
!="ptp" and net_type
!="data":
1970 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1975 if net_type
!="bridge_man" and net_type
!="bridge_data":
1976 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1978 net_type
='bridge_man'
1981 net_type
='bridge_man'
1983 if net_provider
!= None:
1984 if net_provider
[:7]=='bridge:':
1985 #check it is one of the pre-provisioned bridges
1986 bridge_net_name
= net_provider
[7:]
1987 for brnet
in config_dic
['bridge_nets']:
1988 if brnet
[0]==bridge_net_name
: # free
1989 if brnet
[3] != None:
1990 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1995 # if bridge_net==None:
1996 # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
1998 elif config_dic
['network_type'] == 'bridge' and ( net_type
=='bridge_data' or net_type
== 'bridge_man' ):
1999 #look for a free precreated nets
2000 for brnet
in config_dic
['bridge_nets']:
2001 if brnet
[3]==None: # free
2002 if bridge_net
!= None:
2003 if net_type
=='bridge_man': #look for the smaller speed
2004 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
2005 else: #look for the larger speed
2006 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
2010 if bridge_net
==None:
2011 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
2014 print "using net", bridge_net
2015 net_provider
= "bridge:"+bridge_net
[0]
2016 net_vlan
= bridge_net
[1]
2017 elif net_type
== 'bridge_data' or net_type
== 'bridge_man' and config_dic
['network_type'] == 'ovs':
2018 net_provider
= 'OVS'
2019 if not net_vlan
and (net_type
== "data" or net_type
== "ptp" or net_provider
== "OVS"):
2020 net_vlan
= my
.db
.get_free_net_vlan()
2022 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
2024 if net_provider
== 'OVS':
2025 net_provider
= 'OVS' + ":" + str(net_vlan
)
2027 network
['provider'] = net_provider
2028 network
['type'] = net_type
2029 network
['vlan'] = net_vlan
2030 dhcp_integrity
= True
2031 if 'enable_dhcp' in network
and network
['enable_dhcp']:
2032 dhcp_integrity
= check_dhcp_data_integrity(network
)
2034 result
, content
= my
.db
.new_row('nets', network
, True, True)
2036 if result
>= 0 and dhcp_integrity
:
2037 if bridge_net
!=None:
2038 bridge_net
[3] = content
2039 if config_dic
.get("dhcp_server") and config_dic
['network_type'] == 'bridge':
2040 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
2041 config_dic
["dhcp_nets"].append(content
)
2042 print "dhcp_server: add new net", content
2043 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2044 config_dic
["dhcp_nets"].append(content
)
2045 print "dhcp_server: add new net", content
2046 return http_get_network_id(content
)
2048 print "http_post_networks error %d %s" % (result
, content
)
2049 bottle
.abort(-result
, content
)
2053 def check_dhcp_data_integrity(network
):
2055 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
2056 :param network: list with user nets paramters
2061 if "cidr" in network
:
2062 cidr
= network
["cidr"]
2063 ip_tools
= IPNetwork(cidr
)
2064 cidr_len
= ip_tools
.prefixlen
2068 ips
= IPNetwork(cidr
)
2069 if "dhcp_first_ip" not in network
:
2070 network
["dhcp_first_ip"] = str(ips
[2])
2071 if "dhcp_last_ip" not in network
:
2072 network
["dhcp_last_ip"] = str(ips
[-2])
2079 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
2080 def http_put_network_id(network_id
):
2081 '''update a network_id into the database.'''
2082 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2084 http_content
= format_in( network_update_schema
)
2085 r
= remove_extra_items(http_content
, network_update_schema
)
2086 change_keys_http2db(http_content
['network'], http2db_network
)
2087 network
=http_content
['network']
2089 #Look for the previous data
2090 where_
= {'uuid': network_id
}
2091 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
2093 print "http_put_network_id error %d %s" % (result
, network_old
)
2094 bottle
.abort(-result
, network_old
)
2097 print "http_put_network_id network '%s' not found" % network_id
2098 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
2101 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
2102 WHERE
={'net_id': network_id
}, LIMIT
=100)
2104 print "http_put_network_id error %d %s" % (result
, network_old
)
2105 bottle
.abort(-result
, content
)
2108 if 'type' in network
and network
['type'] != network_old
[0]['type']:
2109 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
2110 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
2111 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
2114 net_provider
= network
.get('provider', network_old
[0]['provider'])
2115 net_type
= network
.get('type', network_old
[0]['type'])
2116 net_bind_net
= network
.get("bind_net")
2117 net_bind_type
= network
.get("bind_type")
2118 if net_bind_net
!= None:
2119 #look for a valid net
2120 if check_valid_uuid(net_bind_net
):
2121 net_bind_key
= "uuid"
2123 net_bind_key
= "name"
2124 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
2126 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
2129 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
2132 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
2134 network
["bind_net"] = content
[0]["uuid"]
2135 if net_bind_type
!= None:
2136 if net_bind_type
[0:5] != "vlan:":
2137 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
2139 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
2140 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
2142 if net_provider
!=None:
2143 if net_provider
[:9]=="openflow:":
2144 if net_type
!="ptp" and net_type
!="data":
2145 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2147 if net_type
!="bridge_man" and net_type
!="bridge_data":
2148 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2150 #insert in data base
2151 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
2153 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
2154 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
2156 print "http_put_network_id error while launching openflow rules"
2157 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2158 if config_dic
.get("dhcp_server"):
2159 if network_id
in config_dic
["dhcp_nets"]:
2160 config_dic
["dhcp_nets"].remove(network_id
)
2161 print "dhcp_server: delete net", network_id
2162 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
2163 config_dic
["dhcp_nets"].append(network_id
)
2164 print "dhcp_server: add new net", network_id
2166 net_bind
= network
.get("bind", network_old
["bind"] )
2167 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2168 config_dic
["dhcp_nets"].append(network_id
)
2169 print "dhcp_server: add new net", network_id
2170 return http_get_network_id(network_id
)
2172 bottle
.abort(-result
, content
)
2176 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
2177 def http_delete_network_id(network_id
):
2178 '''delete a network_id from the database.'''
2179 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2181 #delete from the data base
2182 result
, content
= my
.db
.delete_row('nets', network_id
)
2185 bottle
.abort(HTTP_Not_Found
, content
)
2187 for brnet
in config_dic
['bridge_nets']:
2188 if brnet
[3]==network_id
:
2191 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
2192 config_dic
["dhcp_nets"].remove(network_id
)
2193 print "dhcp_server: delete net", network_id
2194 data
={'result' : content
}
2195 return format_out(data
)
2197 print "http_delete_network_id error",result
, content
2198 bottle
.abort(-result
, content
)
2203 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
2204 def http_get_openflow_id(network_id
):
2205 '''To obtain the list of openflow rules of a network
2207 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2209 if network_id
=='all':
2212 where_
={"net_id": network_id
}
2213 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2214 WHERE
=where_
, FROM
='of_flows')
2216 bottle
.abort(-result
, content
)
2218 data
={'openflow-rules' : content
}
2219 return format_out(data
)
2221 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
2222 def http_put_openflow_id(network_id
):
2223 '''To make actions over the net. The action is to reinstall the openflow rules
2224 network_id can be 'all'
2226 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2228 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2231 if network_id
=='all':
2234 where_
={"uuid": network_id
}
2235 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
2237 bottle
.abort(-result
, content
)
2241 if net
["type"]!="ptp" and net
["type"]!="data":
2244 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
2246 print "http_put_openflow_id error while launching openflow rules"
2247 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2248 data
={'result' : str(result
)+" nets updates"}
2249 return format_out(data
)
2251 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
2252 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
2253 def http_clear_openflow_rules():
2254 '''To make actions over the net. The action is to delete ALL openflow rules
2256 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2258 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2261 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
2263 print "http_delete_openflow_id error while launching openflow rules"
2264 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2267 data
={'result' : " Clearing openflow rules in process"}
2268 return format_out(data
)
2270 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
2271 def http_get_openflow_ports():
2272 '''Obtain switch ports names of openflow controller
2274 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2275 return format_out(data
)
2282 @bottle.route(url_base
+ '/ports', method
='GET')
2283 def http_get_ports():
2285 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2286 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2287 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2288 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2290 ports
= my
.ovim
.get_ports(columns
=select_
, filter=where_
, limit
=limit_
)
2292 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2293 data
={'ports' : ports
}
2294 return format_out(data
)
2295 except ovim
.ovimException
as e
:
2296 my
.logger
.error(str(e
), exc_info
=True)
2297 bottle
.abort(e
.http_code
, str(e
))
2298 except Exception as e
:
2299 my
.logger
.error(str(e
), exc_info
=True)
2300 bottle
.abort(HTTP_Bad_Request
, str(e
))
2302 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2303 def http_get_port_id(port_id
):
2304 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2306 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2308 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2311 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2312 data
= {'port': ports
[0]}
2313 return format_out(data
)
2314 except ovim
.ovimException
as e
:
2315 my
.logger
.error(str(e
), exc_info
=True)
2316 bottle
.abort(e
.http_code
, str(e
))
2317 except Exception as e
:
2318 my
.logger
.error(str(e
), exc_info
=True)
2319 bottle
.abort(HTTP_Bad_Request
, str(e
))
2321 @bottle.route(url_base
+ '/ports', method
='POST')
2322 def http_post_ports():
2323 '''insert an external port into the database.'''
2324 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2326 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2328 http_content
= format_in( port_new_schema
)
2329 r
= remove_extra_items(http_content
, port_new_schema
)
2330 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2331 change_keys_http2db(http_content
['port'], http2db_port
)
2332 port
=http_content
['port']
2334 port_id
= my
.ovim
.new_port(port
)
2335 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2337 bottle
.abort(HTTP_Internal_Server_Error
, "port '{}' inserted but not found at database".format(port_id
))
2340 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2341 data
= {'port': ports
[0]}
2342 return format_out(data
)
2343 except ovim
.ovimException
as e
:
2344 my
.logger
.error(str(e
), exc_info
=True)
2345 bottle
.abort(e
.http_code
, str(e
))
2346 except Exception as e
:
2347 my
.logger
.error(str(e
), exc_info
=True)
2348 bottle
.abort(HTTP_Bad_Request
, str(e
))
2350 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2351 def http_put_port_id(port_id
):
2352 '''update a port_id into the database.'''
2353 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2355 http_content
= format_in( port_update_schema
)
2356 change_keys_http2db(http_content
['port'], http2db_port
)
2357 port_dict
=http_content
['port']
2359 for k
in ('vlan', 'switch_port', 'mac_address', 'tenant_id'):
2360 if k
in port_dict
and not my
.admin
:
2361 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2364 port_id
= my
.ovim
.edit_port(port_id
, port_dict
, my
.admin
)
2365 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2367 bottle
.abort(HTTP_Internal_Server_Error
, "port '{}' edited but not found at database".format(port_id
))
2370 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2371 data
= {'port': ports
[0]}
2372 return format_out(data
)
2373 except ovim
.ovimException
as e
:
2374 my
.logger
.error(str(e
), exc_info
=True)
2375 bottle
.abort(e
.http_code
, str(e
))
2376 except Exception as e
:
2377 my
.logger
.error(str(e
), exc_info
=True)
2378 bottle
.abort(HTTP_Bad_Request
, str(e
))
2381 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2382 def http_delete_port_id(port_id
):
2383 '''delete a port_id from the database.'''
2384 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2386 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2389 result
= my
.ovim
.delete_port(port_id
)
2390 data
= {'result': result
}
2391 return format_out(data
)
2392 except ovim
.ovimException
as e
:
2393 my
.logger
.error(str(e
), exc_info
=True)
2394 bottle
.abort(e
.http_code
, str(e
))
2395 except Exception as e
:
2396 my
.logger
.error(str(e
), exc_info
=True)
2397 bottle
.abort(HTTP_Bad_Request
, str(e
))