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 get_dhcp_controller():
666 Create an host_thread object for manage openvim controller and not create a thread for itself
667 :return: dhcp_host openvim controller object
670 if 'openvim_controller' in config_dic
['host_threads']:
671 return config_dic
['host_threads']['openvim_controller']
674 controller_ip
= config_dic
['ovs_controller_ip']
675 ovs_controller_user
= config_dic
['ovs_controller_user']
677 host_test_mode
= True if config_dic
['mode'] == 'test' or config_dic
['mode'] == "OF only" else False
678 host_develop_mode
= True if config_dic
['mode'] == 'development' else False
680 dhcp_host
= ht
.host_thread(name
='openvim_controller', user
=ovs_controller_user
, host
=controller_ip
, db
=config_dic
['db'],
681 db_lock
=config_dic
['db_lock'], test
=host_test_mode
,
682 image_path
=config_dic
['image_path'], version
=config_dic
['version'],
683 host_id
='openvim_controller', develop_mode
=host_develop_mode
,
684 develop_bridge_iface
=bridge_ifaces
)
686 config_dic
['host_threads']['openvim_controller'] = dhcp_host
687 dhcp_host
.ssh_connect()
691 def delete_dhcp_ovs_bridge(vlan
, net_uuid
):
693 Delete bridges and port created during dhcp launching at openvim controller
694 :param vlan: net vlan id
695 :param net_uuid: network identifier
698 dhcp_path
= config_dic
['ovs_controller_file_path']
700 controller_host
= get_dhcp_controller()
701 controller_host
.delete_dhcp_port(vlan
, net_uuid
)
702 controller_host
.delete_dhcp_server(vlan
, net_uuid
, dhcp_path
)
705 def create_dhcp_ovs_bridge():
707 Initialize bridge to allocate the dhcp server at openvim controller
710 controller_host
= get_dhcp_controller()
711 controller_host
.create_ovs_bridge()
714 def set_mac_dhcp(vm_ip
, vlan
, first_ip
, last_ip
, cidr
, mac
):
716 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
717 :param vm_ip: IP address asigned to a VM
718 :param vlan: Segmentation id
719 :param first_ip: First dhcp range ip
720 :param last_ip: Last dhcp range ip
721 :param cidr: net cidr
722 :param mac: VM vnic mac to be macthed with the IP received
726 ip_tools
= IPNetwork(cidr
)
727 cidr_len
= ip_tools
.prefixlen
728 dhcp_netmask
= str(ip_tools
.netmask
)
729 dhcp_path
= config_dic
['ovs_controller_file_path']
731 new_cidr
= [first_ip
+ '/' + str(cidr_len
)]
732 if not len(all_matching_cidrs(vm_ip
, new_cidr
)):
735 controller_host
= get_dhcp_controller()
736 controller_host
.set_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_netmask
, dhcp_path
)
739 def delete_mac_dhcp(vm_ip
, vlan
, mac
):
741 Delete into dhcp conf file the ip assigned to a specific MAC address
742 :param vm_ip: IP address asigned to a VM
743 :param vlan: Segmentation id
744 :param mac: VM vnic mac to be macthed with the IP received
748 dhcp_path
= config_dic
['ovs_controller_file_path']
750 controller_host
= get_dhcp_controller()
751 controller_host
.delete_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_path
)
754 def launch_dhcp_server(vlan
, first_ip
, last_ip
, cidr
):
756 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
757 :param vlan: vlan identifier
758 :param first_ip: First dhcp range ip
759 :param last_ip: Last dhcp range ip
760 :param cidr: net cidr
763 ip_tools
= IPNetwork(cidr
)
764 dhcp_netmask
= str(ip_tools
.netmask
)
765 ip_range
= [first_ip
, last_ip
]
766 dhcp_path
= config_dic
['ovs_controller_file_path']
768 controller_host
= get_dhcp_controller()
769 controller_host
.create_linux_bridge(vlan
)
770 controller_host
.create_dhcp_interfaces(vlan
, first_ip
, dhcp_netmask
)
771 controller_host
.launch_dhcp_server(vlan
, ip_range
, dhcp_netmask
, dhcp_path
)
774 def create_vxlan_mesh(host_id
):
776 Create vxlan mesh across all openvimc controller and computes.
777 :param host_id: host identifier
778 :param host_id: host identifier
781 dhcp_compute_name
= get_vxlan_interface("dhcp")
782 existing_hosts
= get_hosts()
783 if len(existing_hosts
['hosts']) > 0:
784 # vlxan mesh creation between openvim controller and computes
785 computes_available
= existing_hosts
['hosts']
786 controller_host
= get_dhcp_controller()
787 for compute
in computes_available
:
788 vxlan_interface_name
= get_vxlan_interface(compute
['id'][:8])
789 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan", dhcp_compute_name
, controller_host
.host
)
790 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute
['ip_name'])
792 # vlxan mesh creation between openvim computes
793 for count
, compute_owner
in enumerate(computes_available
):
794 for compute
in computes_available
:
795 if compute_owner
['id'] == compute
['id']:
798 vxlan_interface_name
= get_vxlan_interface(compute_owner
['id'][:8])
799 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute_owner
['ip_name'])
800 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan",
801 vxlan_interface_name
,
802 compute_owner
['ip_name'])
805 def delete_vxlan_mesh(host_id
):
807 Create a task for remove a specific compute of the vlxan mesh
808 :param host_id: host id to be deleted.
810 existing_hosts
= get_hosts()
811 computes_available
= existing_hosts
['hosts']
813 vxlan_interface_name
= get_vxlan_interface(host_id
[:8])
814 controller_host
= get_dhcp_controller()
815 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
816 # remove bridge from openvim controller if no more computes exist
817 if len(existing_hosts
):
818 controller_host
.delete_ovs_bridge()
820 for compute
in computes_available
:
821 if host_id
== compute
['id']:
824 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
825 config_dic
['host_threads'][compute
['id']].insert_task("del-vxlan", vxlan_interface_name
)
828 def get_vxlan_interface(local_uuid
):
830 Genearte a vxlan interface name
831 :param local_uuid: host id
832 :return: vlxan-8digits
834 return 'vxlan-' + local_uuid
[:8]
837 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
838 def http_put_host_id(host_id
):
839 '''modify a host into the database. All resources are got and inserted'''
840 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
843 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
846 http_content
= format_in( host_edit_schema
)
847 r
= remove_extra_items(http_content
, host_edit_schema
)
848 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
849 change_keys_http2db(http_content
['host'], http2db_host
)
852 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
854 convert_boolean(content
, ('admin_state_up',) )
855 change_keys_http2db(content
, http2db_host
, reverse
=True)
856 data
={'host' : content
}
858 if config_dic
['network_type'] == 'ovs':
859 delete_vxlan_mesh(host_id
)
860 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
863 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
864 config_dic
['host_threads'][host_id
].user
= content
['user']
865 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
866 config_dic
['host_threads'][host_id
].insert_task("reload")
868 if config_dic
['network_type'] == 'ovs':
869 # create mesh with new host data
870 config_dic
['host_threads'][host_id
].insert_task("new-ovsbridge")
871 create_vxlan_mesh(host_id
)
874 return format_out(data
)
876 bottle
.abort(HTTP_Bad_Request
, content
)
881 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
882 def http_delete_host_id(host_id
):
883 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
886 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
887 result
, content
= my
.db
.delete_row('hosts', host_id
)
889 bottle
.abort(HTTP_Not_Found
, content
)
891 if config_dic
['network_type'] == 'ovs':
892 delete_vxlan_mesh(host_id
)
894 if host_id
in config_dic
['host_threads']:
895 if config_dic
['network_type'] == 'ovs':
896 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
897 config_dic
['host_threads'][host_id
].insert_task("exit")
899 data
={'result' : content
}
900 return format_out(data
)
902 print "http_delete_host_id error",result
, content
903 bottle
.abort(-result
, content
)
912 @bottle.route(url_base
+ '/tenants', method
='GET')
913 def http_get_tenants():
914 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
915 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
916 ('id','name','description','enabled') )
917 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
919 print "http_get_tenants Error", content
920 bottle
.abort(-result
, content
)
922 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
923 convert_boolean(content
, ('enabled',))
924 data
={'tenants' : content
}
925 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
926 return format_out(data
)
928 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
929 def http_get_tenant_id(tenant_id
):
930 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
931 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
933 print "http_get_tenant_id error %d %s" % (result
, content
)
934 bottle
.abort(-result
, content
)
936 print "http_get_tenant_id tenant '%s' not found" % tenant_id
937 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
939 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
940 convert_boolean(content
, ('enabled',))
941 data
={'tenant' : content
[0]}
942 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
943 return format_out(data
)
946 @bottle.route(url_base
+ '/tenants', method
='POST')
947 def http_post_tenants():
948 '''insert a tenant into the database.'''
949 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
951 http_content
= format_in( tenant_new_schema
)
952 r
= remove_extra_items(http_content
, tenant_new_schema
)
953 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
954 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
957 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
960 return http_get_tenant_id(content
)
962 bottle
.abort(-result
, content
)
965 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
966 def http_put_tenant_id(tenant_id
):
967 '''update a tenant into the database.'''
968 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
970 http_content
= format_in( tenant_edit_schema
)
971 r
= remove_extra_items(http_content
, tenant_edit_schema
)
972 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
973 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
976 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
978 return http_get_tenant_id(tenant_id
)
980 bottle
.abort(-result
, content
)
983 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
984 def http_delete_tenant_id(tenant_id
):
985 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
987 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
990 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
993 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
995 bottle
.abort(HTTP_Not_Found
, content
)
997 print "alf", tenants_flavors
, tenants_images
998 for flavor
in tenants_flavors
:
999 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
1000 for image
in tenants_images
:
1001 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
1002 data
={'result' : content
}
1003 return format_out(data
)
1005 print "http_delete_tenant_id error",result
, content
1006 bottle
.abort(-result
, content
)
1013 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
1014 def http_get_flavors(tenant_id
):
1015 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1016 #check valid tenant_id
1017 result
,content
= check_valid_tenant(my
, tenant_id
)
1019 bottle
.abort(result
, content
)
1021 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1022 ('id','name','description','public') )
1023 if tenant_id
=='any':
1026 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
1027 where_
['tenant_id'] = tenant_id
1028 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
1030 print "http_get_flavors Error", content
1031 bottle
.abort(-result
, content
)
1033 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1035 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
1036 data
={'flavors' : content
}
1037 return format_out(data
)
1039 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
1040 def http_get_flavor_id(tenant_id
, flavor_id
):
1041 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1042 #check valid tenant_id
1043 result
,content
= check_valid_tenant(my
, tenant_id
)
1045 bottle
.abort(result
, content
)
1047 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1048 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1049 if tenant_id
=='any':
1052 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1053 where_
['tenant_id'] = tenant_id
1054 where_
['uuid'] = flavor_id
1055 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1058 print "http_get_flavor_id error %d %s" % (result
, content
)
1059 bottle
.abort(-result
, content
)
1061 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
1062 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
1064 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1065 if 'extended' in content
[0] and content
[0]['extended'] is not None:
1066 extended
= json
.loads(content
[0]['extended'])
1067 if 'devices' in extended
:
1068 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
1069 content
[0]['extended']=extended
1070 convert_bandwidth(content
[0], reverse
=True)
1071 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1072 data
={'flavor' : content
[0]}
1073 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1074 return format_out(data
)
1077 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
1078 def http_post_flavors(tenant_id
):
1079 '''insert a flavor into the database, and attach to tenant.'''
1080 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1081 #check valid tenant_id
1082 result
,content
= check_valid_tenant(my
, tenant_id
)
1084 bottle
.abort(result
, content
)
1085 http_content
= format_in( flavor_new_schema
)
1086 r
= remove_extra_items(http_content
, flavor_new_schema
)
1087 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
1088 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1089 extended_dict
= http_content
['flavor'].pop('extended', None)
1090 if extended_dict
is not None:
1091 result
, content
= check_extended(extended_dict
)
1093 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
1094 bottle
.abort(-result
, content
)
1096 convert_bandwidth(extended_dict
)
1097 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1098 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1099 #insert in data base
1100 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
1102 return http_get_flavor_id(tenant_id
, content
)
1104 print "http_psot_flavors error %d %s" % (result
, content
)
1105 bottle
.abort(-result
, content
)
1108 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
1109 def http_delete_flavor_id(tenant_id
, flavor_id
):
1110 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1111 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1112 #check valid tenant_id
1113 result
,content
= check_valid_tenant(my
, tenant_id
)
1115 bottle
.abort(result
, content
)
1117 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
1119 bottle
.abort(HTTP_Not_Found
, content
)
1121 data
={'result' : content
}
1122 return format_out(data
)
1124 print "http_delete_flavor_id error",result
, content
1125 bottle
.abort(-result
, content
)
1128 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
1129 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
1130 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1131 #TODO alf: not tested at all!!!
1132 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1133 #check valid tenant_id
1134 result
,content
= check_valid_tenant(my
, tenant_id
)
1136 bottle
.abort(result
, content
)
1137 if tenant_id
=='any':
1138 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1140 if action
!='attach' and action
!= 'detach':
1141 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1144 #Ensure that flavor exist
1145 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1146 where_
={'uuid': flavor_id
}
1147 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1149 if action
=='attach':
1150 text_error
="Flavor '%s' not found" % flavor_id
1152 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
1153 bottle
.abort(HTTP_Not_Found
, text_error
)
1157 if action
=='attach':
1158 if flavor
['tenant_id']!=None:
1159 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
1160 if flavor
['public']=='no' and not my
.admin
:
1161 #allow only attaching public flavors
1162 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
1164 #insert in data base
1165 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
1167 return http_get_flavor_id(tenant_id
, flavor_id
)
1169 if flavor
['tenant_id']==None:
1170 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
1171 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
1173 if flavor
['public']=='no':
1174 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1175 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
1176 data
={'result' : "flavor detached"}
1177 return format_out(data
)
1179 #if get here is because an error
1180 print "http_attach_detach_flavors error %d %s" % (result
, content
)
1181 bottle
.abort(-result
, content
)
1184 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
1185 def http_put_flavor_id(tenant_id
, flavor_id
):
1186 '''update a flavor_id into the database.'''
1187 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1188 #check valid tenant_id
1189 result
,content
= check_valid_tenant(my
, tenant_id
)
1191 bottle
.abort(result
, content
)
1193 http_content
= format_in( flavor_update_schema
)
1194 r
= remove_extra_items(http_content
, flavor_update_schema
)
1195 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1196 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1197 extended_dict
= http_content
['flavor'].pop('extended', None)
1198 if extended_dict
is not None:
1199 result
, content
= check_extended(extended_dict
)
1201 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
1202 bottle
.abort(-result
, content
)
1204 convert_bandwidth(extended_dict
)
1205 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1206 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1207 #Ensure that flavor exist
1208 where_
={'uuid': flavor_id
}
1209 if tenant_id
=='any':
1212 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1213 where_
['tenant_id'] = tenant_id
1214 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1216 text_error
="Flavor '%s' not found" % flavor_id
1217 if tenant_id
!='any':
1218 text_error
+=" for tenant '%s'" % flavor_id
1219 bottle
.abort(HTTP_Not_Found
, text_error
)
1222 if content
[0]['public']=='yes' and not my
.admin
:
1223 #allow only modifications over private flavors
1224 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1226 #insert in data base
1227 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1230 print "http_put_flavor_id error %d %s" % (result
, content
)
1231 bottle
.abort(-result
, content
)
1234 return http_get_flavor_id(tenant_id
, flavor_id
)
1242 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1243 def http_get_images(tenant_id
):
1244 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1245 #check valid tenant_id
1246 result
,content
= check_valid_tenant(my
, tenant_id
)
1248 bottle
.abort(result
, content
)
1250 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1251 ('id','name','checksum','description','path','public') )
1252 if tenant_id
=='any':
1256 from_
='tenants_images right join images on tenants_images.image_id=images.uuid'
1257 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1258 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1260 print "http_get_images Error", content
1261 bottle
.abort(-result
, content
)
1263 change_keys_http2db(content
, http2db_image
, reverse
=True)
1264 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1265 data
={'images' : content
}
1266 return format_out(data
)
1268 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1269 def http_get_image_id(tenant_id
, image_id
):
1270 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1271 #check valid tenant_id
1272 result
,content
= check_valid_tenant(my
, tenant_id
)
1274 bottle
.abort(result
, content
)
1276 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1277 ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
1278 if tenant_id
=='any':
1282 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1283 where_or_
= {'tenant_id': tenant_id
, 'public': "yes"}
1284 where_
['uuid'] = image_id
1285 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1288 print "http_get_images error %d %s" % (result
, content
)
1289 bottle
.abort(-result
, content
)
1291 print "http_get_images image '%s' not found" % str(image_id
)
1292 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1294 convert_datetime2str(content
)
1295 change_keys_http2db(content
, http2db_image
, reverse
=True)
1296 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1297 metadata
= json
.loads(content
[0]['metadata'])
1298 content
[0]['metadata']=metadata
1299 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1300 data
={'image' : content
[0]}
1301 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1302 return format_out(data
)
1304 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1305 def http_post_images(tenant_id
):
1306 '''insert a image into the database, and attach to tenant.'''
1307 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1308 #check valid tenant_id
1309 result
,content
= check_valid_tenant(my
, tenant_id
)
1311 bottle
.abort(result
, content
)
1312 http_content
= format_in(image_new_schema
)
1313 r
= remove_extra_items(http_content
, image_new_schema
)
1314 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1315 change_keys_http2db(http_content
['image'], http2db_image
)
1316 metadata_dict
= http_content
['image'].pop('metadata', None)
1317 if metadata_dict
is not None:
1318 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1321 image_file
= http_content
['image'].get('path',None)
1322 parsed_url
= urlparse
.urlparse(image_file
)
1323 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "":
1324 # The path is a local file
1325 if os
.path
.exists(image_file
):
1326 http_content
['image']['checksum'] = md5(image_file
)
1328 # The path is a URL. Code should be added to download the image and calculate the checksum
1329 #http_content['image']['checksum'] = md5(downloaded_image)
1331 # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
1332 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1334 if 'checksum' not in http_content
['image']:
1335 http_content
['image']['checksum'] = md5_string(image_file
)
1337 # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
1338 # If it is a URL, no error is sent. Checksum will be an empty string
1339 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "" and 'checksum' not in http_content
['image']:
1340 content
= "Image file not found"
1341 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1342 bottle
.abort(HTTP_Bad_Request
, content
)
1343 except Exception as e
:
1344 print "ERROR. Unexpected exception: %s" % (str(e
))
1345 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1346 #insert in data base
1347 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1349 return http_get_image_id(tenant_id
, content
)
1351 print "http_post_images error %d %s" % (result
, content
)
1352 bottle
.abort(-result
, content
)
1355 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1356 def http_delete_image_id(tenant_id
, image_id
):
1357 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1358 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1359 #check valid tenant_id
1360 result
,content
= check_valid_tenant(my
, tenant_id
)
1362 bottle
.abort(result
, content
)
1363 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1365 bottle
.abort(HTTP_Not_Found
, content
)
1367 data
={'result' : content
}
1368 return format_out(data
)
1370 print "http_delete_image_id error",result
, content
1371 bottle
.abort(-result
, content
)
1374 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1375 def http_attach_detach_images(tenant_id
, image_id
, action
):
1376 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1377 #TODO alf: not tested at all!!!
1378 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1379 #check valid tenant_id
1380 result
,content
= check_valid_tenant(my
, tenant_id
)
1382 bottle
.abort(result
, content
)
1383 if tenant_id
=='any':
1384 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1386 if action
!='attach' and action
!= 'detach':
1387 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1390 #Ensure that image exist
1391 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1392 where_
={'uuid': image_id
}
1393 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1395 if action
=='attach':
1396 text_error
="Image '%s' not found" % image_id
1398 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1399 bottle
.abort(HTTP_Not_Found
, text_error
)
1403 if action
=='attach':
1404 if image
['tenant_id']!=None:
1405 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1406 if image
['public']=='no' and not my
.admin
:
1407 #allow only attaching public images
1408 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1410 #insert in data base
1411 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1413 return http_get_image_id(tenant_id
, image_id
)
1415 if image
['tenant_id']==None:
1416 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1417 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1419 if image
['public']=='no':
1420 #try to delete the image completely to avoid orphan images, IGNORE error
1421 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1422 data
={'result' : "image detached"}
1423 return format_out(data
)
1425 #if get here is because an error
1426 print "http_attach_detach_images error %d %s" % (result
, content
)
1427 bottle
.abort(-result
, content
)
1430 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1431 def http_put_image_id(tenant_id
, image_id
):
1432 '''update a image_id into the database.'''
1433 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1434 #check valid tenant_id
1435 result
,content
= check_valid_tenant(my
, tenant_id
)
1437 bottle
.abort(result
, content
)
1439 http_content
= format_in( image_update_schema
)
1440 r
= remove_extra_items(http_content
, image_update_schema
)
1441 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1442 change_keys_http2db(http_content
['image'], http2db_image
)
1443 metadata_dict
= http_content
['image'].pop('metadata', None)
1444 if metadata_dict
is not None:
1445 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1446 #Ensure that image exist
1447 where_
={'uuid': image_id
}
1448 if tenant_id
=='any':
1452 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1453 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1454 result
, content
= my
.db
.get_table(SELECT
=('public',), DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND")
1456 text_error
="Image '%s' not found" % image_id
1457 if tenant_id
!='any':
1458 text_error
+=" for tenant '%s'" % image_id
1459 bottle
.abort(HTTP_Not_Found
, text_error
)
1462 if content
[0]['public']=='yes' and not my
.admin
:
1463 #allow only modifications over private images
1464 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1466 #insert in data base
1467 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1470 print "http_put_image_id error %d %s" % (result
, content
)
1471 bottle
.abort(-result
, content
)
1474 return http_get_image_id(tenant_id
, image_id
)
1481 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1482 def http_get_servers(tenant_id
):
1483 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1484 result
,content
= check_valid_tenant(my
, tenant_id
)
1486 bottle
.abort(result
, content
)
1489 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1490 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1491 if tenant_id
!='any':
1492 where_
['tenant_id'] = tenant_id
1493 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1495 print "http_get_servers Error", content
1496 bottle
.abort(-result
, content
)
1498 change_keys_http2db(content
, http2db_server
, reverse
=True)
1500 tenant_id
= row
.pop('tenant_id')
1501 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1502 data
={'servers' : content
}
1503 return format_out(data
)
1505 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1506 def http_get_server_id(tenant_id
, server_id
):
1507 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1508 #check valid tenant_id
1509 result
,content
= check_valid_tenant(my
, tenant_id
)
1511 bottle
.abort(result
, content
)
1514 result
, content
= my
.db
.get_instance(server_id
)
1516 bottle
.abort(HTTP_Not_Found
, content
)
1518 #change image/flavor-id to id and link
1519 convert_bandwidth(content
, reverse
=True)
1520 convert_datetime2str(content
)
1521 if content
["ram"]==0 : del content
["ram"]
1522 if content
["vcpus"]==0 : del content
["vcpus"]
1523 if 'flavor_id' in content
:
1524 if content
['flavor_id'] is not None:
1525 content
['flavor'] = {'id':content
['flavor_id'],
1526 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1528 del content
['flavor_id']
1529 if 'image_id' in content
:
1530 if content
['image_id'] is not None:
1531 content
['image'] = {'id':content
['image_id'],
1532 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1534 del content
['image_id']
1535 change_keys_http2db(content
, http2db_server
, reverse
=True)
1536 if 'extended' in content
:
1537 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1539 data
={'server' : content
}
1540 return format_out(data
)
1542 bottle
.abort(-result
, content
)
1545 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1546 def http_post_server_id(tenant_id
):
1547 '''deploys a new server'''
1548 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1549 #check valid tenant_id
1550 result
,content
= check_valid_tenant(my
, tenant_id
)
1552 bottle
.abort(result
, content
)
1554 if tenant_id
=='any':
1555 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1557 http_content
= format_in( server_new_schema
)
1558 r
= remove_extra_items(http_content
, server_new_schema
)
1559 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1560 change_keys_http2db(http_content
['server'], http2db_server
)
1561 extended_dict
= http_content
['server'].get('extended', None)
1562 if extended_dict
is not None:
1563 result
, content
= check_extended(extended_dict
, True)
1565 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1566 bottle
.abort(-result
, content
)
1568 convert_bandwidth(extended_dict
)
1569 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1571 server
= http_content
['server']
1572 server_start
= server
.get('start', 'yes')
1573 server
['tenant_id'] = tenant_id
1574 #check flavor valid and take info
1575 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1576 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1578 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1580 server
['flavor']=content
[0]
1581 #check image valid and take info
1582 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1583 SELECT
=('path', 'metadata', 'image_id'),
1584 WHERE
={'uuid':server
['image_id'], "status":"ACTIVE"},
1585 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'},
1589 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1591 for image_dict
in content
:
1592 if image_dict
.get("image_id"):
1595 # insert in data base tenants_images
1596 r2
, c2
= my
.db
.new_row('tenants_images', {'image_id': server
['image_id'], 'tenant_id': tenant_id
})
1598 bottle
.abort(HTTP_Not_Found
, 'image_id %s cannot be used. Error %s' % (server
['image_id'], c2
))
1600 server
['image']={"path": content
[0]["path"], "metadata": content
[0]["metadata"]}
1601 if "hosts_id" in server
:
1602 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1604 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1606 #print json.dumps(server, indent=4)
1608 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1611 #Insert instance to database
1614 print "inserting at DB"
1616 if server_start
== 'no':
1617 content
['status'] = 'INACTIVE'
1619 for net
in http_content
['server']['networks']:
1620 if net
['type'] == 'instance:ovs':
1621 dhcp_nets_id
.append(get_network_id(net
['net_id']))
1624 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1625 if new_instance_result
< 0:
1626 print "Error http_post_servers() :", new_instance_result
, new_instance
1627 bottle
.abort(-new_instance_result
, new_instance
)
1630 print "inserted at DB"
1632 for port
in ports_to_free
:
1633 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1635 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1638 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1640 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1643 #look for dhcp ip address
1644 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "ip_address", "net_id"], WHERE
={"instance_id": new_instance
})
1647 if config_dic
.get("dhcp_server") and iface
["net_id"] in config_dic
["dhcp_nets"]:
1648 #print "dhcp insert add task"
1649 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1651 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1653 #ensure compute contain the bridge for ovs networks:
1654 server_net
= get_network_id(iface
['net_id'])
1655 if server_net
["network"].get('provider:physical', "")[:3] == 'OVS':
1656 vlan
= str(server_net
['network']['provider:vlan'])
1657 dhcp_enable
= bool(server_net
['network']['enable_dhcp'])
1659 dhcp_firt_ip
= str(server_net
['network']['dhcp_first_ip'])
1660 dhcp_last_ip
= str(server_net
['network']['dhcp_last_ip'])
1661 dhcp_cidr
= str(server_net
['network']['cidr'])
1662 vm_dhcp_ip
= c2
[0]["ip_address"]
1663 config_dic
['host_threads'][server
['host_id']].insert_task("create-ovs-bridge-port", vlan
)
1665 set_mac_dhcp(vm_dhcp_ip
, vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
, c2
[0]['mac'])
1666 launch_dhcp_server(vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
)
1669 server
['uuid'] = new_instance
1670 server_start
= server
.get('start', 'yes')
1672 if server_start
!= 'no':
1673 server
['paused'] = True if server_start
== 'paused' else False
1674 server
['action'] = {"start":None}
1675 server
['status'] = "CREATING"
1677 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1679 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1681 return http_get_server_id(tenant_id
, new_instance
)
1683 bottle
.abort(HTTP_Bad_Request
, content
)
1686 def http_server_action(server_id
, tenant_id
, action
):
1687 '''Perform actions over a server as resume, reboot, terminate, ...'''
1688 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1689 server
={"uuid": server_id
, "action":action
}
1690 where
={'uuid': server_id
}
1691 if tenant_id
!='any':
1692 where
['tenant_id']= tenant_id
1693 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1695 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1698 print "http_post_server_action error getting data %d %s" % (result
, content
)
1699 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1701 server
.update(content
[0])
1702 tenant_id
= server
["tenant_id"]
1704 #TODO check a right content
1706 if 'terminate' in action
:
1707 new_status
='DELETING'
1708 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1709 if 'terminate' not in action
and 'rebuild' not in action
:
1710 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1712 # elif server['status'] == 'INACTIVE':
1713 # if 'start' not in action and 'createImage' not in action:
1714 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1716 # if 'start' in action:
1717 # new_status='CREATING'
1718 # server['paused']='no'
1719 # elif server['status'] == 'PAUSED':
1720 # if 'resume' not in action:
1721 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1723 # elif server['status'] == 'ACTIVE':
1724 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1725 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1728 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1729 #check image valid and take info
1730 image_id
= server
['image_id']
1731 if 'createImage' in action
:
1732 if 'imageRef' in action
['createImage']:
1733 image_id
= action
['createImage']['imageRef']
1734 elif 'disk' in action
['createImage']:
1735 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1736 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1738 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1742 if action
['createImage']['imageRef']['disk'] != None:
1743 for disk
in content
:
1744 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1745 disk_id
= disk
['image_id']
1748 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1751 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1755 image_id
= content
[0]['image_id']
1757 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1758 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, "status":"ACTIVE"},
1759 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'}, WHERE_AND_OR
="AND", DISTINCT
=True)
1761 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1763 if content
[0]['metadata'] is not None:
1765 metadata
= json
.loads(content
[0]['metadata'])
1767 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1768 content
[0]['metadata']=metadata
1770 content
[0]['metadata'] = {}
1771 server
['image']=content
[0]
1772 if 'createImage' in action
:
1773 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1774 if 'createImage' in action
:
1775 #Create an entry in Database for the new image
1776 new_image
={'status':'BUILD', 'progress': 0 }
1777 new_image_metadata
=content
[0]
1778 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1779 new_image_metadata
.update(server
['image']['metadata'])
1780 new_image_metadata
= {"use_incremental":"no"}
1781 if 'metadata' in action
['createImage']:
1782 new_image_metadata
.update(action
['createImage']['metadata'])
1783 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1784 new_image
['name'] = action
['createImage'].get('name', None)
1785 new_image
['description'] = action
['createImage'].get('description', None)
1786 new_image
['uuid']=my
.db
.new_uuid()
1787 if 'path' in action
['createImage']:
1788 new_image
['path'] = action
['createImage']['path']
1790 new_image
['path']="/provisional/path/" + new_image
['uuid']
1791 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1793 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1795 server
['new_image'] = new_image
1799 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1801 print "Task queue full at host ", server
['host_id']
1802 bottle
.abort(HTTP_Request_Timeout
, c
)
1803 if 'createImage' in action
and result
>= 0:
1804 return http_get_image_id(tenant_id
, image_uuid
)
1806 #Update DB only for CREATING or DELETING status
1807 data
={'result' : 'in process'}
1808 if new_status
!= None and new_status
== 'DELETING':
1813 #look for dhcp ip address
1814 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1815 r
, c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, net_ovs_list
, "requested by http")
1816 for port
in ports_to_free
:
1817 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1819 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1820 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1822 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1824 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1825 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1826 #look for dhcp ip address
1827 if r2
>0 and config_dic
.get("dhcp_server"):
1829 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1830 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1831 #print "dhcp insert del task"
1833 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1834 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1835 for net
in net_ovs_list
:
1840 delete_dhcp_ovs_bridge(vlan
, net_id
)
1841 delete_mac_dhcp(vm_ip
, vlan
, mac
)
1842 config_dic
['host_threads'][server
['host_id']].insert_task('del-ovs-port', vlan
, net_id
)
1843 return format_out(data
)
1847 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1848 def http_delete_server_id(tenant_id
, server_id
):
1849 '''delete a server'''
1850 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1851 #check valid tenant_id
1852 result
,content
= check_valid_tenant(my
, tenant_id
)
1854 bottle
.abort(result
, content
)
1857 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1860 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1861 def http_post_server_action(tenant_id
, server_id
):
1862 '''take an action over a server'''
1863 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1864 #check valid tenant_id
1865 result
,content
= check_valid_tenant(my
, tenant_id
)
1867 bottle
.abort(result
, content
)
1869 http_content
= format_in( server_action_schema
)
1870 #r = remove_extra_items(http_content, server_action_schema)
1871 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1873 return http_server_action(server_id
, tenant_id
, http_content
)
1880 @bottle.route(url_base
+ '/networks', method
='GET')
1881 def http_get_networks():
1882 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1884 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1885 ('id','name','tenant_id','type',
1886 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1887 #TODO temporally remove tenant_id
1888 if "tenant_id" in where_
:
1889 del where_
["tenant_id"]
1890 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1892 print "http_get_networks error %d %s" % (result
, content
)
1893 bottle
.abort(-result
, content
)
1895 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1896 delete_nulls(content
)
1897 change_keys_http2db(content
, http2db_network
, reverse
=True)
1898 data
={'networks' : content
}
1899 return format_out(data
)
1901 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1902 def http_get_network_id(network_id
):
1903 data
= get_network_id(network_id
)
1904 return format_out(data
)
1906 def get_network_id(network_id
):
1907 my
= config_dic
['http_threads'][threading
.current_thread().name
]
1909 where_
= bottle
.request
.query
1910 where_
['uuid'] = network_id
1911 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1914 print "http_get_networks_id error %d %s" % (result
, content
)
1915 bottle
.abort(-result
, content
)
1917 print "http_get_networks_id network '%s' not found" % network_id
1918 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1920 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1921 change_keys_http2db(content
, http2db_network
, reverse
=True)
1923 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1924 WHERE
={'net_id': network_id
}, LIMIT
=100)
1926 content
[0]['ports'] = ports
1927 delete_nulls(content
[0])
1928 data
={'network' : content
[0]}
1931 @bottle.route(url_base
+ '/networks', method
='POST')
1932 def http_post_networks():
1933 '''insert a network into the database.'''
1934 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1936 http_content
= format_in( network_new_schema
)
1937 r
= remove_extra_items(http_content
, network_new_schema
)
1938 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1939 change_keys_http2db(http_content
['network'], http2db_network
)
1940 network
=http_content
['network']
1941 #check valid tenant_id
1942 tenant_id
= network
.get('tenant_id')
1944 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1946 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1950 net_provider
= network
.get('provider')
1951 net_type
= network
.get('type')
1952 net_enable_dhcp
= network
.get('enable_dhcp')
1954 net_cidr
= network
.get('cidr')
1956 net_vlan
= network
.get("vlan")
1957 net_bind_net
= network
.get("bind_net")
1958 net_bind_type
= network
.get("bind_type")
1959 name
= network
["name"]
1961 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1962 vlan_index
=name
.rfind(":")
1963 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1965 vlan_tag
= int(name
[vlan_index
+1:])
1966 if vlan_tag
>0 and vlan_tag
< 4096:
1967 net_bind_net
= name
[:vlan_index
]
1968 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1972 if net_bind_net
!= None:
1973 #look for a valid net
1974 if check_valid_uuid(net_bind_net
):
1975 net_bind_key
= "uuid"
1977 net_bind_key
= "name"
1978 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1980 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1983 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1986 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1988 network
["bind_net"] = content
[0]["uuid"]
1989 if net_bind_type
!= None:
1990 if net_bind_type
[0:5] != "vlan:":
1991 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1993 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1994 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1996 network
["bind_type"] = net_bind_type
1998 if net_provider
!=None:
1999 if net_provider
[:9]=="openflow:":
2001 if net_type
!="ptp" and net_type
!="data":
2002 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2007 if net_type
!="bridge_man" and net_type
!="bridge_data":
2008 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2010 net_type
='bridge_man'
2013 net_type
='bridge_man'
2015 if net_provider
!= None:
2016 if net_provider
[:7]=='bridge:':
2017 #check it is one of the pre-provisioned bridges
2018 bridge_net_name
= net_provider
[7:]
2019 for brnet
in config_dic
['bridge_nets']:
2020 if brnet
[0]==bridge_net_name
: # free
2021 if brnet
[3] != None:
2022 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
2027 # if bridge_net==None:
2028 # 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)
2030 elif config_dic
['network_type'] == 'bridge' and ( net_type
=='bridge_data' or net_type
== 'bridge_man' ):
2031 #look for a free precreated nets
2032 for brnet
in config_dic
['bridge_nets']:
2033 if brnet
[3]==None: # free
2034 if bridge_net
!= None:
2035 if net_type
=='bridge_man': #look for the smaller speed
2036 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
2037 else: #look for the larger speed
2038 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
2042 if bridge_net
==None:
2043 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
2046 print "using net", bridge_net
2047 net_provider
= "bridge:"+bridge_net
[0]
2048 net_vlan
= bridge_net
[1]
2049 elif net_type
== 'bridge_data' or net_type
== 'bridge_man' and config_dic
['network_type'] == 'ovs':
2050 net_provider
= 'OVS'
2051 if not net_vlan
and (net_type
== "data" or net_type
== "ptp" or net_provider
== "OVS"):
2052 net_vlan
= my
.db
.get_free_net_vlan()
2054 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
2056 if net_provider
== 'OVS':
2057 net_provider
= 'OVS' + ":" + str(net_vlan
)
2059 network
['provider'] = net_provider
2060 network
['type'] = net_type
2061 network
['vlan'] = net_vlan
2063 if 'enable_dhcp' in network
and network
['enable_dhcp']:
2064 check_dhcp_data_integrity(network
)
2066 result
, content
= my
.db
.new_row('nets', network
, True, True)
2069 if bridge_net
!=None:
2070 bridge_net
[3] = content
2071 if config_dic
.get("dhcp_server") and config_dic
['network_type'] == 'bridge':
2072 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
2073 config_dic
["dhcp_nets"].append(content
)
2074 print "dhcp_server: add new net", content
2075 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2076 config_dic
["dhcp_nets"].append(content
)
2077 print "dhcp_server: add new net", content
2078 return http_get_network_id(content
)
2080 print "http_post_networks error %d %s" % (result
, content
)
2081 bottle
.abort(-result
, content
)
2085 def check_dhcp_data_integrity(network
):
2087 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
2088 :param network: list with user nets paramters
2093 if "cidr" in network
:
2094 cidr
= network
["cidr"]
2096 ips
= IPNetwork(cidr
)
2097 if "dhcp_first_ip" not in network
:
2098 network
["dhcp_first_ip"] = str(ips
[2])
2099 if "dhcp_last_ip" not in network
:
2100 network
["dhcp_last_ip"] = str(ips
[-2])
2103 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
2104 def http_put_network_id(network_id
):
2105 '''update a network_id into the database.'''
2106 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2108 http_content
= format_in( network_update_schema
)
2109 r
= remove_extra_items(http_content
, network_update_schema
)
2110 change_keys_http2db(http_content
['network'], http2db_network
)
2111 network
=http_content
['network']
2113 #Look for the previous data
2114 where_
= {'uuid': network_id
}
2115 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
2117 print "http_put_network_id error %d %s" % (result
, network_old
)
2118 bottle
.abort(-result
, network_old
)
2121 print "http_put_network_id network '%s' not found" % network_id
2122 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
2125 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
2126 WHERE
={'net_id': network_id
}, LIMIT
=100)
2128 print "http_put_network_id error %d %s" % (result
, network_old
)
2129 bottle
.abort(-result
, content
)
2132 if 'type' in network
and network
['type'] != network_old
[0]['type']:
2133 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
2134 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
2135 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
2138 net_provider
= network
.get('provider', network_old
[0]['provider'])
2139 net_type
= network
.get('type', network_old
[0]['type'])
2140 net_bind_net
= network
.get("bind_net")
2141 net_bind_type
= network
.get("bind_type")
2142 if net_bind_net
!= None:
2143 #look for a valid net
2144 if check_valid_uuid(net_bind_net
):
2145 net_bind_key
= "uuid"
2147 net_bind_key
= "name"
2148 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
2150 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
2153 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
2156 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
2158 network
["bind_net"] = content
[0]["uuid"]
2159 if net_bind_type
!= None:
2160 if net_bind_type
[0:5] != "vlan:":
2161 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
2163 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
2164 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
2166 if net_provider
!=None:
2167 if net_provider
[:9]=="openflow:":
2168 if net_type
!="ptp" and net_type
!="data":
2169 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2171 if net_type
!="bridge_man" and net_type
!="bridge_data":
2172 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2174 #insert in data base
2175 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
2177 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
2178 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
2180 print "http_put_network_id error while launching openflow rules"
2181 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2182 if config_dic
.get("dhcp_server"):
2183 if network_id
in config_dic
["dhcp_nets"]:
2184 config_dic
["dhcp_nets"].remove(network_id
)
2185 print "dhcp_server: delete net", network_id
2186 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
2187 config_dic
["dhcp_nets"].append(network_id
)
2188 print "dhcp_server: add new net", network_id
2190 net_bind
= network
.get("bind", network_old
["bind"] )
2191 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2192 config_dic
["dhcp_nets"].append(network_id
)
2193 print "dhcp_server: add new net", network_id
2194 return http_get_network_id(network_id
)
2196 bottle
.abort(-result
, content
)
2200 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
2201 def http_delete_network_id(network_id
):
2202 '''delete a network_id from the database.'''
2203 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2205 #delete from the data base
2206 result
, content
= my
.db
.delete_row('nets', network_id
)
2209 bottle
.abort(HTTP_Not_Found
, content
)
2211 for brnet
in config_dic
['bridge_nets']:
2212 if brnet
[3]==network_id
:
2215 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
2216 config_dic
["dhcp_nets"].remove(network_id
)
2217 print "dhcp_server: delete net", network_id
2218 data
={'result' : content
}
2219 return format_out(data
)
2221 print "http_delete_network_id error",result
, content
2222 bottle
.abort(-result
, content
)
2227 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
2228 def http_get_openflow_id(network_id
):
2229 '''To obtain the list of openflow rules of a network
2231 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2233 if network_id
=='all':
2236 where_
={"net_id": network_id
}
2237 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2238 WHERE
=where_
, FROM
='of_flows')
2240 bottle
.abort(-result
, content
)
2242 data
={'openflow-rules' : content
}
2243 return format_out(data
)
2245 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
2246 def http_put_openflow_id(network_id
):
2247 '''To make actions over the net. The action is to reinstall the openflow rules
2248 network_id can be 'all'
2250 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2252 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2255 if network_id
=='all':
2258 where_
={"uuid": network_id
}
2259 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
2261 bottle
.abort(-result
, content
)
2265 if net
["type"]!="ptp" and net
["type"]!="data":
2268 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
2270 print "http_put_openflow_id error while launching openflow rules"
2271 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2272 data
={'result' : str(result
)+" nets updates"}
2273 return format_out(data
)
2275 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
2276 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
2277 def http_clear_openflow_rules():
2278 '''To make actions over the net. The action is to delete ALL openflow rules
2280 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2282 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2285 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
2287 print "http_delete_openflow_id error while launching openflow rules"
2288 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2291 data
={'result' : " Clearing openflow rules in process"}
2292 return format_out(data
)
2294 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
2295 def http_get_openflow_ports():
2296 '''Obtain switch ports names of openflow controller
2298 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2299 return format_out(data
)
2306 @bottle.route(url_base
+ '/ports', method
='GET')
2307 def http_get_ports():
2309 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2310 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2311 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2312 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2314 ports
= my
.ovim
.get_ports(columns
=select_
, filter=where_
, limit
=limit_
)
2316 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2317 data
={'ports' : ports
}
2318 return format_out(data
)
2319 except ovim
.ovimException
as e
:
2320 my
.logger
.error(str(e
), exc_info
=True)
2321 bottle
.abort(e
.http_code
, str(e
))
2322 except Exception as e
:
2323 my
.logger
.error(str(e
), exc_info
=True)
2324 bottle
.abort(HTTP_Bad_Request
, str(e
))
2326 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2327 def http_get_port_id(port_id
):
2328 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2330 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2332 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2335 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2336 data
= {'port': ports
[0]}
2337 return format_out(data
)
2338 except ovim
.ovimException
as e
:
2339 my
.logger
.error(str(e
), exc_info
=True)
2340 bottle
.abort(e
.http_code
, str(e
))
2341 except Exception as e
:
2342 my
.logger
.error(str(e
), exc_info
=True)
2343 bottle
.abort(HTTP_Bad_Request
, str(e
))
2345 @bottle.route(url_base
+ '/ports', method
='POST')
2346 def http_post_ports():
2347 '''insert an external port into the database.'''
2348 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2350 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2352 http_content
= format_in( port_new_schema
)
2353 r
= remove_extra_items(http_content
, port_new_schema
)
2354 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2355 change_keys_http2db(http_content
['port'], http2db_port
)
2356 port
=http_content
['port']
2358 port_id
= my
.ovim
.new_port(port
)
2359 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2361 bottle
.abort(HTTP_Internal_Server_Error
, "port '{}' inserted but not found at database".format(port_id
))
2364 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2365 data
= {'port': ports
[0]}
2366 return format_out(data
)
2367 except ovim
.ovimException
as e
:
2368 my
.logger
.error(str(e
), exc_info
=True)
2369 bottle
.abort(e
.http_code
, str(e
))
2370 except Exception as e
:
2371 my
.logger
.error(str(e
), exc_info
=True)
2372 bottle
.abort(HTTP_Bad_Request
, str(e
))
2374 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2375 def http_put_port_id(port_id
):
2376 '''update a port_id into the database.'''
2377 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2379 http_content
= format_in( port_update_schema
)
2380 change_keys_http2db(http_content
['port'], http2db_port
)
2381 port_dict
=http_content
['port']
2383 for k
in ('vlan', 'switch_port', 'mac_address', 'tenant_id'):
2384 if k
in port_dict
and not my
.admin
:
2385 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2388 port_id
= my
.ovim
.edit_port(port_id
, port_dict
, my
.admin
)
2389 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2391 bottle
.abort(HTTP_Internal_Server_Error
, "port '{}' edited but not found at database".format(port_id
))
2394 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2395 data
= {'port': ports
[0]}
2396 return format_out(data
)
2397 except ovim
.ovimException
as e
:
2398 my
.logger
.error(str(e
), exc_info
=True)
2399 bottle
.abort(e
.http_code
, str(e
))
2400 except Exception as e
:
2401 my
.logger
.error(str(e
), exc_info
=True)
2402 bottle
.abort(HTTP_Bad_Request
, str(e
))
2405 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2406 def http_delete_port_id(port_id
):
2407 '''delete a port_id from the database.'''
2408 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2410 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2413 result
= my
.ovim
.delete_port(port_id
)
2414 data
= {'result': result
}
2415 return format_out(data
)
2416 except ovim
.ovimException
as e
:
2417 my
.logger
.error(str(e
), exc_info
=True)
2418 bottle
.abort(e
.http_code
, str(e
))
2419 except Exception as e
:
2420 my
.logger
.error(str(e
), exc_info
=True)
2421 bottle
.abort(HTTP_Bad_Request
, str(e
))