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 if not host_test_mode
:
688 dhcp_host
.ssh_connect()
692 def delete_dhcp_ovs_bridge(vlan
, net_uuid
):
694 Delete bridges and port created during dhcp launching at openvim controller
695 :param vlan: net vlan id
696 :param net_uuid: network identifier
699 dhcp_path
= config_dic
['ovs_controller_file_path']
701 controller_host
= get_dhcp_controller()
702 controller_host
.delete_dhcp_port(vlan
, net_uuid
)
703 controller_host
.delete_dhcp_server(vlan
, net_uuid
, dhcp_path
)
706 def create_dhcp_ovs_bridge():
708 Initialize bridge to allocate the dhcp server at openvim controller
711 controller_host
= get_dhcp_controller()
712 controller_host
.create_ovs_bridge()
715 def set_mac_dhcp(vm_ip
, vlan
, first_ip
, last_ip
, cidr
, mac
):
717 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
718 :param vm_ip: IP address asigned to a VM
719 :param vlan: Segmentation id
720 :param first_ip: First dhcp range ip
721 :param last_ip: Last dhcp range ip
722 :param cidr: net cidr
723 :param mac: VM vnic mac to be macthed with the IP received
727 ip_tools
= IPNetwork(cidr
)
728 cidr_len
= ip_tools
.prefixlen
729 dhcp_netmask
= str(ip_tools
.netmask
)
730 dhcp_path
= config_dic
['ovs_controller_file_path']
732 new_cidr
= [first_ip
+ '/' + str(cidr_len
)]
733 if not len(all_matching_cidrs(vm_ip
, new_cidr
)):
736 controller_host
= get_dhcp_controller()
737 controller_host
.set_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_netmask
, dhcp_path
)
740 def delete_mac_dhcp(vm_ip
, vlan
, mac
):
742 Delete into dhcp conf file the ip assigned to a specific MAC address
743 :param vm_ip: IP address asigned to a VM
744 :param vlan: Segmentation id
745 :param mac: VM vnic mac to be macthed with the IP received
749 dhcp_path
= config_dic
['ovs_controller_file_path']
751 controller_host
= get_dhcp_controller()
752 controller_host
.delete_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_path
)
755 def launch_dhcp_server(vlan
, first_ip
, last_ip
, cidr
):
757 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
758 :param vlan: vlan identifier
759 :param first_ip: First dhcp range ip
760 :param last_ip: Last dhcp range ip
761 :param cidr: net cidr
764 ip_tools
= IPNetwork(cidr
)
765 dhcp_netmask
= str(ip_tools
.netmask
)
766 ip_range
= [first_ip
, last_ip
]
767 dhcp_path
= config_dic
['ovs_controller_file_path']
769 controller_host
= get_dhcp_controller()
770 controller_host
.create_linux_bridge(vlan
)
771 controller_host
.create_dhcp_interfaces(vlan
, first_ip
, dhcp_netmask
)
772 controller_host
.launch_dhcp_server(vlan
, ip_range
, dhcp_netmask
, dhcp_path
)
775 def create_vxlan_mesh(host_id
):
777 Create vxlan mesh across all openvimc controller and computes.
778 :param host_id: host identifier
779 :param host_id: host identifier
782 dhcp_compute_name
= get_vxlan_interface("dhcp")
783 existing_hosts
= get_hosts()
784 if len(existing_hosts
['hosts']) > 0:
785 # vlxan mesh creation between openvim controller and computes
786 computes_available
= existing_hosts
['hosts']
787 controller_host
= get_dhcp_controller()
788 for compute
in computes_available
:
789 vxlan_interface_name
= get_vxlan_interface(compute
['id'][:8])
790 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan", dhcp_compute_name
, controller_host
.host
)
791 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute
['ip_name'])
793 # vlxan mesh creation between openvim computes
794 for count
, compute_owner
in enumerate(computes_available
):
795 for compute
in computes_available
:
796 if compute_owner
['id'] == compute
['id']:
799 vxlan_interface_name
= get_vxlan_interface(compute_owner
['id'][:8])
800 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute_owner
['ip_name'])
801 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan",
802 vxlan_interface_name
,
803 compute_owner
['ip_name'])
806 def delete_vxlan_mesh(host_id
):
808 Create a task for remove a specific compute of the vlxan mesh
809 :param host_id: host id to be deleted.
811 existing_hosts
= get_hosts()
812 computes_available
= existing_hosts
['hosts']
814 vxlan_interface_name
= get_vxlan_interface(host_id
[:8])
815 controller_host
= get_dhcp_controller()
816 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
817 # remove bridge from openvim controller if no more computes exist
818 if len(existing_hosts
):
819 controller_host
.delete_ovs_bridge()
821 for compute
in computes_available
:
822 if host_id
== compute
['id']:
825 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
826 config_dic
['host_threads'][compute
['id']].insert_task("del-vxlan", vxlan_interface_name
)
829 def get_vxlan_interface(local_uuid
):
831 Genearte a vxlan interface name
832 :param local_uuid: host id
833 :return: vlxan-8digits
835 return 'vxlan-' + local_uuid
[:8]
838 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
839 def http_put_host_id(host_id
):
840 '''modify a host into the database. All resources are got and inserted'''
841 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
844 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
847 http_content
= format_in( host_edit_schema
)
848 r
= remove_extra_items(http_content
, host_edit_schema
)
849 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
850 change_keys_http2db(http_content
['host'], http2db_host
)
853 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
855 convert_boolean(content
, ('admin_state_up',) )
856 change_keys_http2db(content
, http2db_host
, reverse
=True)
857 data
={'host' : content
}
859 if config_dic
['network_type'] == 'ovs':
860 delete_vxlan_mesh(host_id
)
861 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
864 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
865 config_dic
['host_threads'][host_id
].user
= content
['user']
866 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
867 config_dic
['host_threads'][host_id
].insert_task("reload")
869 if config_dic
['network_type'] == 'ovs':
870 # create mesh with new host data
871 config_dic
['host_threads'][host_id
].insert_task("new-ovsbridge")
872 create_vxlan_mesh(host_id
)
875 return format_out(data
)
877 bottle
.abort(HTTP_Bad_Request
, content
)
882 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
883 def http_delete_host_id(host_id
):
884 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
887 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
888 result
, content
= my
.db
.delete_row('hosts', host_id
)
890 bottle
.abort(HTTP_Not_Found
, content
)
892 if config_dic
['network_type'] == 'ovs':
893 delete_vxlan_mesh(host_id
)
895 if host_id
in config_dic
['host_threads']:
896 if config_dic
['network_type'] == 'ovs':
897 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
898 config_dic
['host_threads'][host_id
].insert_task("exit")
900 data
={'result' : content
}
901 return format_out(data
)
903 print "http_delete_host_id error",result
, content
904 bottle
.abort(-result
, content
)
913 @bottle.route(url_base
+ '/tenants', method
='GET')
914 def http_get_tenants():
915 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
916 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
917 ('id','name','description','enabled') )
918 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
920 print "http_get_tenants Error", content
921 bottle
.abort(-result
, content
)
923 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
924 convert_boolean(content
, ('enabled',))
925 data
={'tenants' : content
}
926 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
927 return format_out(data
)
929 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
930 def http_get_tenant_id(tenant_id
):
931 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
932 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
934 print "http_get_tenant_id error %d %s" % (result
, content
)
935 bottle
.abort(-result
, content
)
937 print "http_get_tenant_id tenant '%s' not found" % tenant_id
938 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
940 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
941 convert_boolean(content
, ('enabled',))
942 data
={'tenant' : content
[0]}
943 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
944 return format_out(data
)
947 @bottle.route(url_base
+ '/tenants', method
='POST')
948 def http_post_tenants():
949 '''insert a tenant into the database.'''
950 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
952 http_content
= format_in( tenant_new_schema
)
953 r
= remove_extra_items(http_content
, tenant_new_schema
)
954 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
955 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
958 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
961 return http_get_tenant_id(content
)
963 bottle
.abort(-result
, content
)
966 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
967 def http_put_tenant_id(tenant_id
):
968 '''update a tenant into the database.'''
969 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
971 http_content
= format_in( tenant_edit_schema
)
972 r
= remove_extra_items(http_content
, tenant_edit_schema
)
973 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
974 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
977 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
979 return http_get_tenant_id(tenant_id
)
981 bottle
.abort(-result
, content
)
984 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
985 def http_delete_tenant_id(tenant_id
):
986 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
988 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
991 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
994 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
996 bottle
.abort(HTTP_Not_Found
, content
)
998 print "alf", tenants_flavors
, tenants_images
999 for flavor
in tenants_flavors
:
1000 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
1001 for image
in tenants_images
:
1002 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
1003 data
={'result' : content
}
1004 return format_out(data
)
1006 print "http_delete_tenant_id error",result
, content
1007 bottle
.abort(-result
, content
)
1014 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
1015 def http_get_flavors(tenant_id
):
1016 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1017 #check valid tenant_id
1018 result
,content
= check_valid_tenant(my
, tenant_id
)
1020 bottle
.abort(result
, content
)
1022 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1023 ('id','name','description','public') )
1024 if tenant_id
=='any':
1027 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
1028 where_
['tenant_id'] = tenant_id
1029 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
1031 print "http_get_flavors Error", content
1032 bottle
.abort(-result
, content
)
1034 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1036 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
1037 data
={'flavors' : content
}
1038 return format_out(data
)
1040 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
1041 def http_get_flavor_id(tenant_id
, flavor_id
):
1042 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1043 #check valid tenant_id
1044 result
,content
= check_valid_tenant(my
, tenant_id
)
1046 bottle
.abort(result
, content
)
1048 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1049 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1050 if tenant_id
=='any':
1053 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1054 where_
['tenant_id'] = tenant_id
1055 where_
['uuid'] = flavor_id
1056 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1059 print "http_get_flavor_id error %d %s" % (result
, content
)
1060 bottle
.abort(-result
, content
)
1062 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
1063 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
1065 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1066 if 'extended' in content
[0] and content
[0]['extended'] is not None:
1067 extended
= json
.loads(content
[0]['extended'])
1068 if 'devices' in extended
:
1069 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
1070 content
[0]['extended']=extended
1071 convert_bandwidth(content
[0], reverse
=True)
1072 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1073 data
={'flavor' : content
[0]}
1074 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1075 return format_out(data
)
1078 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
1079 def http_post_flavors(tenant_id
):
1080 '''insert a flavor into the database, and attach to tenant.'''
1081 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1082 #check valid tenant_id
1083 result
,content
= check_valid_tenant(my
, tenant_id
)
1085 bottle
.abort(result
, content
)
1086 http_content
= format_in( flavor_new_schema
)
1087 r
= remove_extra_items(http_content
, flavor_new_schema
)
1088 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
1089 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1090 extended_dict
= http_content
['flavor'].pop('extended', None)
1091 if extended_dict
is not None:
1092 result
, content
= check_extended(extended_dict
)
1094 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
1095 bottle
.abort(-result
, content
)
1097 convert_bandwidth(extended_dict
)
1098 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1099 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1100 #insert in data base
1101 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
1103 return http_get_flavor_id(tenant_id
, content
)
1105 print "http_psot_flavors error %d %s" % (result
, content
)
1106 bottle
.abort(-result
, content
)
1109 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
1110 def http_delete_flavor_id(tenant_id
, flavor_id
):
1111 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1112 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1113 #check valid tenant_id
1114 result
,content
= check_valid_tenant(my
, tenant_id
)
1116 bottle
.abort(result
, content
)
1118 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
1120 bottle
.abort(HTTP_Not_Found
, content
)
1122 data
={'result' : content
}
1123 return format_out(data
)
1125 print "http_delete_flavor_id error",result
, content
1126 bottle
.abort(-result
, content
)
1129 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
1130 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
1131 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1132 #TODO alf: not tested at all!!!
1133 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1134 #check valid tenant_id
1135 result
,content
= check_valid_tenant(my
, tenant_id
)
1137 bottle
.abort(result
, content
)
1138 if tenant_id
=='any':
1139 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1141 if action
!='attach' and action
!= 'detach':
1142 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1145 #Ensure that flavor exist
1146 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1147 where_
={'uuid': flavor_id
}
1148 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1150 if action
=='attach':
1151 text_error
="Flavor '%s' not found" % flavor_id
1153 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
1154 bottle
.abort(HTTP_Not_Found
, text_error
)
1158 if action
=='attach':
1159 if flavor
['tenant_id']!=None:
1160 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
1161 if flavor
['public']=='no' and not my
.admin
:
1162 #allow only attaching public flavors
1163 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
1165 #insert in data base
1166 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
1168 return http_get_flavor_id(tenant_id
, flavor_id
)
1170 if flavor
['tenant_id']==None:
1171 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
1172 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
1174 if flavor
['public']=='no':
1175 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1176 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
1177 data
={'result' : "flavor detached"}
1178 return format_out(data
)
1180 #if get here is because an error
1181 print "http_attach_detach_flavors error %d %s" % (result
, content
)
1182 bottle
.abort(-result
, content
)
1185 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
1186 def http_put_flavor_id(tenant_id
, flavor_id
):
1187 '''update a flavor_id into the database.'''
1188 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1189 #check valid tenant_id
1190 result
,content
= check_valid_tenant(my
, tenant_id
)
1192 bottle
.abort(result
, content
)
1194 http_content
= format_in( flavor_update_schema
)
1195 r
= remove_extra_items(http_content
, flavor_update_schema
)
1196 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1197 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1198 extended_dict
= http_content
['flavor'].pop('extended', None)
1199 if extended_dict
is not None:
1200 result
, content
= check_extended(extended_dict
)
1202 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
1203 bottle
.abort(-result
, content
)
1205 convert_bandwidth(extended_dict
)
1206 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1207 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1208 #Ensure that flavor exist
1209 where_
={'uuid': flavor_id
}
1210 if tenant_id
=='any':
1213 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1214 where_
['tenant_id'] = tenant_id
1215 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1217 text_error
="Flavor '%s' not found" % flavor_id
1218 if tenant_id
!='any':
1219 text_error
+=" for tenant '%s'" % flavor_id
1220 bottle
.abort(HTTP_Not_Found
, text_error
)
1223 if content
[0]['public']=='yes' and not my
.admin
:
1224 #allow only modifications over private flavors
1225 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1227 #insert in data base
1228 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1231 print "http_put_flavor_id error %d %s" % (result
, content
)
1232 bottle
.abort(-result
, content
)
1235 return http_get_flavor_id(tenant_id
, flavor_id
)
1243 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1244 def http_get_images(tenant_id
):
1245 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1246 #check valid tenant_id
1247 result
,content
= check_valid_tenant(my
, tenant_id
)
1249 bottle
.abort(result
, content
)
1251 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1252 ('id','name','checksum','description','path','public') )
1253 if tenant_id
=='any':
1257 from_
='tenants_images right join images on tenants_images.image_id=images.uuid'
1258 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1259 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1261 print "http_get_images Error", content
1262 bottle
.abort(-result
, content
)
1264 change_keys_http2db(content
, http2db_image
, reverse
=True)
1265 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1266 data
={'images' : content
}
1267 return format_out(data
)
1269 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1270 def http_get_image_id(tenant_id
, image_id
):
1271 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1272 #check valid tenant_id
1273 result
,content
= check_valid_tenant(my
, tenant_id
)
1275 bottle
.abort(result
, content
)
1277 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1278 ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
1279 if tenant_id
=='any':
1283 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1284 where_or_
= {'tenant_id': tenant_id
, 'public': "yes"}
1285 where_
['uuid'] = image_id
1286 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1289 print "http_get_images error %d %s" % (result
, content
)
1290 bottle
.abort(-result
, content
)
1292 print "http_get_images image '%s' not found" % str(image_id
)
1293 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1295 convert_datetime2str(content
)
1296 change_keys_http2db(content
, http2db_image
, reverse
=True)
1297 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1298 metadata
= json
.loads(content
[0]['metadata'])
1299 content
[0]['metadata']=metadata
1300 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1301 data
={'image' : content
[0]}
1302 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1303 return format_out(data
)
1305 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1306 def http_post_images(tenant_id
):
1307 '''insert a image into the database, and attach to tenant.'''
1308 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1309 #check valid tenant_id
1310 result
,content
= check_valid_tenant(my
, tenant_id
)
1312 bottle
.abort(result
, content
)
1313 http_content
= format_in(image_new_schema
)
1314 r
= remove_extra_items(http_content
, image_new_schema
)
1315 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1316 change_keys_http2db(http_content
['image'], http2db_image
)
1317 metadata_dict
= http_content
['image'].pop('metadata', None)
1318 if metadata_dict
is not None:
1319 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1322 image_file
= http_content
['image'].get('path',None)
1323 parsed_url
= urlparse
.urlparse(image_file
)
1324 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "":
1325 # The path is a local file
1326 if os
.path
.exists(image_file
):
1327 http_content
['image']['checksum'] = md5(image_file
)
1329 # The path is a URL. Code should be added to download the image and calculate the checksum
1330 #http_content['image']['checksum'] = md5(downloaded_image)
1332 # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
1333 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1335 if 'checksum' not in http_content
['image']:
1336 http_content
['image']['checksum'] = md5_string(image_file
)
1338 # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
1339 # If it is a URL, no error is sent. Checksum will be an empty string
1340 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "" and 'checksum' not in http_content
['image']:
1341 content
= "Image file not found"
1342 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1343 bottle
.abort(HTTP_Bad_Request
, content
)
1344 except Exception as e
:
1345 print "ERROR. Unexpected exception: %s" % (str(e
))
1346 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1347 #insert in data base
1348 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1350 return http_get_image_id(tenant_id
, content
)
1352 print "http_post_images error %d %s" % (result
, content
)
1353 bottle
.abort(-result
, content
)
1356 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1357 def http_delete_image_id(tenant_id
, image_id
):
1358 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1359 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1360 #check valid tenant_id
1361 result
,content
= check_valid_tenant(my
, tenant_id
)
1363 bottle
.abort(result
, content
)
1364 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1366 bottle
.abort(HTTP_Not_Found
, content
)
1368 data
={'result' : content
}
1369 return format_out(data
)
1371 print "http_delete_image_id error",result
, content
1372 bottle
.abort(-result
, content
)
1375 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1376 def http_attach_detach_images(tenant_id
, image_id
, action
):
1377 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1378 #TODO alf: not tested at all!!!
1379 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1380 #check valid tenant_id
1381 result
,content
= check_valid_tenant(my
, tenant_id
)
1383 bottle
.abort(result
, content
)
1384 if tenant_id
=='any':
1385 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1387 if action
!='attach' and action
!= 'detach':
1388 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1391 #Ensure that image exist
1392 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1393 where_
={'uuid': image_id
}
1394 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1396 if action
=='attach':
1397 text_error
="Image '%s' not found" % image_id
1399 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1400 bottle
.abort(HTTP_Not_Found
, text_error
)
1404 if action
=='attach':
1405 if image
['tenant_id']!=None:
1406 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1407 if image
['public']=='no' and not my
.admin
:
1408 #allow only attaching public images
1409 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1411 #insert in data base
1412 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1414 return http_get_image_id(tenant_id
, image_id
)
1416 if image
['tenant_id']==None:
1417 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1418 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1420 if image
['public']=='no':
1421 #try to delete the image completely to avoid orphan images, IGNORE error
1422 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1423 data
={'result' : "image detached"}
1424 return format_out(data
)
1426 #if get here is because an error
1427 print "http_attach_detach_images error %d %s" % (result
, content
)
1428 bottle
.abort(-result
, content
)
1431 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1432 def http_put_image_id(tenant_id
, image_id
):
1433 '''update a image_id into the database.'''
1434 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1435 #check valid tenant_id
1436 result
,content
= check_valid_tenant(my
, tenant_id
)
1438 bottle
.abort(result
, content
)
1440 http_content
= format_in( image_update_schema
)
1441 r
= remove_extra_items(http_content
, image_update_schema
)
1442 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1443 change_keys_http2db(http_content
['image'], http2db_image
)
1444 metadata_dict
= http_content
['image'].pop('metadata', None)
1445 if metadata_dict
is not None:
1446 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1447 #Ensure that image exist
1448 where_
={'uuid': image_id
}
1449 if tenant_id
=='any':
1453 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1454 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1455 result
, content
= my
.db
.get_table(SELECT
=('public',), DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND")
1457 text_error
="Image '%s' not found" % image_id
1458 if tenant_id
!='any':
1459 text_error
+=" for tenant '%s'" % image_id
1460 bottle
.abort(HTTP_Not_Found
, text_error
)
1463 if content
[0]['public']=='yes' and not my
.admin
:
1464 #allow only modifications over private images
1465 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1467 #insert in data base
1468 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1471 print "http_put_image_id error %d %s" % (result
, content
)
1472 bottle
.abort(-result
, content
)
1475 return http_get_image_id(tenant_id
, image_id
)
1482 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1483 def http_get_servers(tenant_id
):
1484 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1485 result
,content
= check_valid_tenant(my
, tenant_id
)
1487 bottle
.abort(result
, content
)
1490 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1491 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1492 if tenant_id
!='any':
1493 where_
['tenant_id'] = tenant_id
1494 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1496 print "http_get_servers Error", content
1497 bottle
.abort(-result
, content
)
1499 change_keys_http2db(content
, http2db_server
, reverse
=True)
1501 tenant_id
= row
.pop('tenant_id')
1502 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1503 data
={'servers' : content
}
1504 return format_out(data
)
1506 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1507 def http_get_server_id(tenant_id
, server_id
):
1508 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1509 #check valid tenant_id
1510 result
,content
= check_valid_tenant(my
, tenant_id
)
1512 bottle
.abort(result
, content
)
1515 result
, content
= my
.db
.get_instance(server_id
)
1517 bottle
.abort(HTTP_Not_Found
, content
)
1519 #change image/flavor-id to id and link
1520 convert_bandwidth(content
, reverse
=True)
1521 convert_datetime2str(content
)
1522 if content
["ram"]==0 : del content
["ram"]
1523 if content
["vcpus"]==0 : del content
["vcpus"]
1524 if 'flavor_id' in content
:
1525 if content
['flavor_id'] is not None:
1526 content
['flavor'] = {'id':content
['flavor_id'],
1527 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1529 del content
['flavor_id']
1530 if 'image_id' in content
:
1531 if content
['image_id'] is not None:
1532 content
['image'] = {'id':content
['image_id'],
1533 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1535 del content
['image_id']
1536 change_keys_http2db(content
, http2db_server
, reverse
=True)
1537 if 'extended' in content
:
1538 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1540 data
={'server' : content
}
1541 return format_out(data
)
1543 bottle
.abort(-result
, content
)
1546 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1547 def http_post_server_id(tenant_id
):
1548 '''deploys a new server'''
1549 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1550 #check valid tenant_id
1551 result
,content
= check_valid_tenant(my
, tenant_id
)
1553 bottle
.abort(result
, content
)
1555 if tenant_id
=='any':
1556 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1558 http_content
= format_in( server_new_schema
)
1559 r
= remove_extra_items(http_content
, server_new_schema
)
1560 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1561 change_keys_http2db(http_content
['server'], http2db_server
)
1562 extended_dict
= http_content
['server'].get('extended', None)
1563 if extended_dict
is not None:
1564 result
, content
= check_extended(extended_dict
, True)
1566 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1567 bottle
.abort(-result
, content
)
1569 convert_bandwidth(extended_dict
)
1570 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1572 server
= http_content
['server']
1573 server_start
= server
.get('start', 'yes')
1574 server
['tenant_id'] = tenant_id
1575 #check flavor valid and take info
1576 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1577 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1579 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1581 server
['flavor']=content
[0]
1582 #check image valid and take info
1583 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1584 SELECT
=('path', 'metadata', 'image_id'),
1585 WHERE
={'uuid':server
['image_id'], "status":"ACTIVE"},
1586 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'},
1590 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1592 for image_dict
in content
:
1593 if image_dict
.get("image_id"):
1596 # insert in data base tenants_images
1597 r2
, c2
= my
.db
.new_row('tenants_images', {'image_id': server
['image_id'], 'tenant_id': tenant_id
})
1599 bottle
.abort(HTTP_Not_Found
, 'image_id %s cannot be used. Error %s' % (server
['image_id'], c2
))
1601 server
['image']={"path": content
[0]["path"], "metadata": content
[0]["metadata"]}
1602 if "hosts_id" in server
:
1603 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1605 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1607 #print json.dumps(server, indent=4)
1609 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1612 #Insert instance to database
1615 print "inserting at DB"
1617 if server_start
== 'no':
1618 content
['status'] = 'INACTIVE'
1620 for net
in http_content
['server']['networks']:
1621 if net
['type'] == 'instance:ovs':
1622 dhcp_nets_id
.append(get_network_id(net
['net_id']))
1625 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1626 if new_instance_result
< 0:
1627 print "Error http_post_servers() :", new_instance_result
, new_instance
1628 bottle
.abort(-new_instance_result
, new_instance
)
1631 print "inserted at DB"
1633 for port
in ports_to_free
:
1634 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1636 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1639 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1641 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1644 #look for dhcp ip address
1645 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "ip_address", "net_id"], WHERE
={"instance_id": new_instance
})
1648 if config_dic
.get("dhcp_server") and iface
["net_id"] in config_dic
["dhcp_nets"]:
1649 #print "dhcp insert add task"
1650 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1652 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1654 #ensure compute contain the bridge for ovs networks:
1655 server_net
= get_network_id(iface
['net_id'])
1656 if server_net
["network"].get('provider:physical', "")[:3] == 'OVS':
1657 vlan
= str(server_net
['network']['provider:vlan'])
1658 dhcp_enable
= bool(server_net
['network']['enable_dhcp'])
1660 dhcp_firt_ip
= str(server_net
['network']['dhcp_first_ip'])
1661 dhcp_last_ip
= str(server_net
['network']['dhcp_last_ip'])
1662 dhcp_cidr
= str(server_net
['network']['cidr'])
1663 vm_dhcp_ip
= c2
[0]["ip_address"]
1664 config_dic
['host_threads'][server
['host_id']].insert_task("create-ovs-bridge-port", vlan
)
1666 set_mac_dhcp(vm_dhcp_ip
, vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
, c2
[0]['mac'])
1667 launch_dhcp_server(vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
)
1670 server
['uuid'] = new_instance
1671 server_start
= server
.get('start', 'yes')
1673 if server_start
!= 'no':
1674 server
['paused'] = True if server_start
== 'paused' else False
1675 server
['action'] = {"start":None}
1676 server
['status'] = "CREATING"
1678 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1680 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1682 return http_get_server_id(tenant_id
, new_instance
)
1684 bottle
.abort(HTTP_Bad_Request
, content
)
1687 def http_server_action(server_id
, tenant_id
, action
):
1688 '''Perform actions over a server as resume, reboot, terminate, ...'''
1689 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1690 server
={"uuid": server_id
, "action":action
}
1691 where
={'uuid': server_id
}
1692 if tenant_id
!='any':
1693 where
['tenant_id']= tenant_id
1694 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1696 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1699 print "http_post_server_action error getting data %d %s" % (result
, content
)
1700 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1702 server
.update(content
[0])
1703 tenant_id
= server
["tenant_id"]
1705 #TODO check a right content
1707 if 'terminate' in action
:
1708 new_status
='DELETING'
1709 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1710 if 'terminate' not in action
and 'rebuild' not in action
:
1711 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1713 # elif server['status'] == 'INACTIVE':
1714 # if 'start' not in action and 'createImage' not in action:
1715 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1717 # if 'start' in action:
1718 # new_status='CREATING'
1719 # server['paused']='no'
1720 # elif server['status'] == 'PAUSED':
1721 # if 'resume' not in action:
1722 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1724 # elif server['status'] == 'ACTIVE':
1725 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1726 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1729 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1730 #check image valid and take info
1731 image_id
= server
['image_id']
1732 if 'createImage' in action
:
1733 if 'imageRef' in action
['createImage']:
1734 image_id
= action
['createImage']['imageRef']
1735 elif 'disk' in action
['createImage']:
1736 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1737 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1739 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1743 if action
['createImage']['imageRef']['disk'] != None:
1744 for disk
in content
:
1745 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1746 disk_id
= disk
['image_id']
1749 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1752 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1756 image_id
= content
[0]['image_id']
1758 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1759 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, "status":"ACTIVE"},
1760 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'}, WHERE_AND_OR
="AND", DISTINCT
=True)
1762 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1764 if content
[0]['metadata'] is not None:
1766 metadata
= json
.loads(content
[0]['metadata'])
1768 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1769 content
[0]['metadata']=metadata
1771 content
[0]['metadata'] = {}
1772 server
['image']=content
[0]
1773 if 'createImage' in action
:
1774 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1775 if 'createImage' in action
:
1776 #Create an entry in Database for the new image
1777 new_image
={'status':'BUILD', 'progress': 0 }
1778 new_image_metadata
=content
[0]
1779 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1780 new_image_metadata
.update(server
['image']['metadata'])
1781 new_image_metadata
= {"use_incremental":"no"}
1782 if 'metadata' in action
['createImage']:
1783 new_image_metadata
.update(action
['createImage']['metadata'])
1784 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1785 new_image
['name'] = action
['createImage'].get('name', None)
1786 new_image
['description'] = action
['createImage'].get('description', None)
1787 new_image
['uuid']=my
.db
.new_uuid()
1788 if 'path' in action
['createImage']:
1789 new_image
['path'] = action
['createImage']['path']
1791 new_image
['path']="/provisional/path/" + new_image
['uuid']
1792 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1794 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1796 server
['new_image'] = new_image
1800 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1802 print "Task queue full at host ", server
['host_id']
1803 bottle
.abort(HTTP_Request_Timeout
, c
)
1804 if 'createImage' in action
and result
>= 0:
1805 return http_get_image_id(tenant_id
, image_uuid
)
1807 #Update DB only for CREATING or DELETING status
1808 data
={'result' : 'in process'}
1809 if new_status
!= None and new_status
== 'DELETING':
1814 #look for dhcp ip address
1815 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1816 r
, c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, net_ovs_list
, "requested by http")
1817 for port
in ports_to_free
:
1818 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1820 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1821 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1823 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1825 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1826 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1827 #look for dhcp ip address
1828 if r2
>0 and config_dic
.get("dhcp_server"):
1830 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1831 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1832 #print "dhcp insert del task"
1834 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1835 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1836 for net
in net_ovs_list
:
1841 delete_dhcp_ovs_bridge(vlan
, net_id
)
1842 delete_mac_dhcp(vm_ip
, vlan
, mac
)
1843 config_dic
['host_threads'][server
['host_id']].insert_task('del-ovs-port', vlan
, net_id
)
1844 return format_out(data
)
1848 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1849 def http_delete_server_id(tenant_id
, server_id
):
1850 '''delete a server'''
1851 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1852 #check valid tenant_id
1853 result
,content
= check_valid_tenant(my
, tenant_id
)
1855 bottle
.abort(result
, content
)
1858 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1861 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1862 def http_post_server_action(tenant_id
, server_id
):
1863 '''take an action over a server'''
1864 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1865 #check valid tenant_id
1866 result
,content
= check_valid_tenant(my
, tenant_id
)
1868 bottle
.abort(result
, content
)
1870 http_content
= format_in( server_action_schema
)
1871 #r = remove_extra_items(http_content, server_action_schema)
1872 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1874 return http_server_action(server_id
, tenant_id
, http_content
)
1881 @bottle.route(url_base
+ '/networks', method
='GET')
1882 def http_get_networks():
1883 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1885 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1886 ('id','name','tenant_id','type',
1887 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1888 #TODO temporally remove tenant_id
1889 if "tenant_id" in where_
:
1890 del where_
["tenant_id"]
1891 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1893 print "http_get_networks error %d %s" % (result
, content
)
1894 bottle
.abort(-result
, content
)
1896 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1897 delete_nulls(content
)
1898 change_keys_http2db(content
, http2db_network
, reverse
=True)
1899 data
={'networks' : content
}
1900 return format_out(data
)
1902 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1903 def http_get_network_id(network_id
):
1904 data
= get_network_id(network_id
)
1905 return format_out(data
)
1907 def get_network_id(network_id
):
1908 my
= config_dic
['http_threads'][threading
.current_thread().name
]
1910 where_
= bottle
.request
.query
1911 where_
['uuid'] = network_id
1912 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1915 print "http_get_networks_id error %d %s" % (result
, content
)
1916 bottle
.abort(-result
, content
)
1918 print "http_get_networks_id network '%s' not found" % network_id
1919 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1921 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1922 change_keys_http2db(content
, http2db_network
, reverse
=True)
1924 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1925 WHERE
={'net_id': network_id
}, LIMIT
=100)
1927 content
[0]['ports'] = ports
1928 delete_nulls(content
[0])
1929 data
={'network' : content
[0]}
1932 @bottle.route(url_base
+ '/networks', method
='POST')
1933 def http_post_networks():
1934 '''insert a network into the database.'''
1935 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1937 http_content
= format_in( network_new_schema
)
1938 r
= remove_extra_items(http_content
, network_new_schema
)
1939 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1940 change_keys_http2db(http_content
['network'], http2db_network
)
1941 network
=http_content
['network']
1942 #check valid tenant_id
1943 tenant_id
= network
.get('tenant_id')
1945 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1947 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1951 net_provider
= network
.get('provider')
1952 net_type
= network
.get('type')
1953 net_enable_dhcp
= network
.get('enable_dhcp')
1955 net_cidr
= network
.get('cidr')
1957 net_vlan
= network
.get("vlan")
1958 net_bind_net
= network
.get("bind_net")
1959 net_bind_type
= network
.get("bind_type")
1960 name
= network
["name"]
1962 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1963 vlan_index
=name
.rfind(":")
1964 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1966 vlan_tag
= int(name
[vlan_index
+1:])
1967 if vlan_tag
>0 and vlan_tag
< 4096:
1968 net_bind_net
= name
[:vlan_index
]
1969 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1973 if net_bind_net
!= None:
1974 #look for a valid net
1975 if check_valid_uuid(net_bind_net
):
1976 net_bind_key
= "uuid"
1978 net_bind_key
= "name"
1979 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1981 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1984 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1987 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1989 network
["bind_net"] = content
[0]["uuid"]
1990 if net_bind_type
!= None:
1991 if net_bind_type
[0:5] != "vlan:":
1992 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1994 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1995 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1997 network
["bind_type"] = net_bind_type
1999 if net_provider
!=None:
2000 if net_provider
[:9]=="openflow:":
2002 if net_type
!="ptp" and net_type
!="data":
2003 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2008 if net_type
!="bridge_man" and net_type
!="bridge_data":
2009 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2011 net_type
='bridge_man'
2014 net_type
='bridge_man'
2016 if net_provider
!= None:
2017 if net_provider
[:7]=='bridge:':
2018 #check it is one of the pre-provisioned bridges
2019 bridge_net_name
= net_provider
[7:]
2020 for brnet
in config_dic
['bridge_nets']:
2021 if brnet
[0]==bridge_net_name
: # free
2022 if brnet
[3] != None:
2023 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
2028 # if bridge_net==None:
2029 # 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)
2031 elif config_dic
['network_type'] == 'bridge' and ( net_type
=='bridge_data' or net_type
== 'bridge_man' ):
2032 #look for a free precreated nets
2033 for brnet
in config_dic
['bridge_nets']:
2034 if brnet
[3]==None: # free
2035 if bridge_net
!= None:
2036 if net_type
=='bridge_man': #look for the smaller speed
2037 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
2038 else: #look for the larger speed
2039 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
2043 if bridge_net
==None:
2044 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
2047 print "using net", bridge_net
2048 net_provider
= "bridge:"+bridge_net
[0]
2049 net_vlan
= bridge_net
[1]
2050 elif net_type
== 'bridge_data' or net_type
== 'bridge_man' and config_dic
['network_type'] == 'ovs':
2051 net_provider
= 'OVS'
2052 if not net_vlan
and (net_type
== "data" or net_type
== "ptp" or net_provider
== "OVS"):
2053 net_vlan
= my
.db
.get_free_net_vlan()
2055 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
2057 if net_provider
== 'OVS':
2058 net_provider
= 'OVS' + ":" + str(net_vlan
)
2060 network
['provider'] = net_provider
2061 network
['type'] = net_type
2062 network
['vlan'] = net_vlan
2064 if 'enable_dhcp' in network
and network
['enable_dhcp']:
2065 check_dhcp_data_integrity(network
)
2067 result
, content
= my
.db
.new_row('nets', network
, True, True)
2070 if bridge_net
!=None:
2071 bridge_net
[3] = content
2072 if config_dic
.get("dhcp_server") and config_dic
['network_type'] == 'bridge':
2073 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
2074 config_dic
["dhcp_nets"].append(content
)
2075 print "dhcp_server: add new net", content
2076 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2077 config_dic
["dhcp_nets"].append(content
)
2078 print "dhcp_server: add new net", content
2079 return http_get_network_id(content
)
2081 print "http_post_networks error %d %s" % (result
, content
)
2082 bottle
.abort(-result
, content
)
2086 def check_dhcp_data_integrity(network
):
2088 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
2089 :param network: list with user nets paramters
2094 if "cidr" in network
:
2095 cidr
= network
["cidr"]
2097 ips
= IPNetwork(cidr
)
2098 if "dhcp_first_ip" not in network
:
2099 network
["dhcp_first_ip"] = str(ips
[2])
2100 if "dhcp_last_ip" not in network
:
2101 network
["dhcp_last_ip"] = str(ips
[-2])
2104 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
2105 def http_put_network_id(network_id
):
2106 '''update a network_id into the database.'''
2107 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2109 http_content
= format_in( network_update_schema
)
2110 r
= remove_extra_items(http_content
, network_update_schema
)
2111 change_keys_http2db(http_content
['network'], http2db_network
)
2112 network
=http_content
['network']
2114 #Look for the previous data
2115 where_
= {'uuid': network_id
}
2116 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
2118 print "http_put_network_id error %d %s" % (result
, network_old
)
2119 bottle
.abort(-result
, network_old
)
2122 print "http_put_network_id network '%s' not found" % network_id
2123 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
2126 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
2127 WHERE
={'net_id': network_id
}, LIMIT
=100)
2129 print "http_put_network_id error %d %s" % (result
, network_old
)
2130 bottle
.abort(-result
, content
)
2133 if 'type' in network
and network
['type'] != network_old
[0]['type']:
2134 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
2135 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
2136 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
2139 net_provider
= network
.get('provider', network_old
[0]['provider'])
2140 net_type
= network
.get('type', network_old
[0]['type'])
2141 net_bind_net
= network
.get("bind_net")
2142 net_bind_type
= network
.get("bind_type")
2143 if net_bind_net
!= None:
2144 #look for a valid net
2145 if check_valid_uuid(net_bind_net
):
2146 net_bind_key
= "uuid"
2148 net_bind_key
= "name"
2149 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
2151 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
2154 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
2157 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
2159 network
["bind_net"] = content
[0]["uuid"]
2160 if net_bind_type
!= None:
2161 if net_bind_type
[0:5] != "vlan:":
2162 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
2164 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
2165 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
2167 if net_provider
!=None:
2168 if net_provider
[:9]=="openflow:":
2169 if net_type
!="ptp" and net_type
!="data":
2170 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2172 if net_type
!="bridge_man" and net_type
!="bridge_data":
2173 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2175 #insert in data base
2176 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
2178 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
2179 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
2181 print "http_put_network_id error while launching openflow rules"
2182 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2183 if config_dic
.get("dhcp_server"):
2184 if network_id
in config_dic
["dhcp_nets"]:
2185 config_dic
["dhcp_nets"].remove(network_id
)
2186 print "dhcp_server: delete net", network_id
2187 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
2188 config_dic
["dhcp_nets"].append(network_id
)
2189 print "dhcp_server: add new net", network_id
2191 net_bind
= network
.get("bind", network_old
["bind"] )
2192 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2193 config_dic
["dhcp_nets"].append(network_id
)
2194 print "dhcp_server: add new net", network_id
2195 return http_get_network_id(network_id
)
2197 bottle
.abort(-result
, content
)
2201 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
2202 def http_delete_network_id(network_id
):
2203 '''delete a network_id from the database.'''
2204 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2206 #delete from the data base
2207 result
, content
= my
.db
.delete_row('nets', network_id
)
2210 bottle
.abort(HTTP_Not_Found
, content
)
2212 for brnet
in config_dic
['bridge_nets']:
2213 if brnet
[3]==network_id
:
2216 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
2217 config_dic
["dhcp_nets"].remove(network_id
)
2218 print "dhcp_server: delete net", network_id
2219 data
={'result' : content
}
2220 return format_out(data
)
2222 print "http_delete_network_id error",result
, content
2223 bottle
.abort(-result
, content
)
2228 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
2229 def http_get_openflow_id(network_id
):
2230 '''To obtain the list of openflow rules of a network
2232 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2234 if network_id
=='all':
2237 where_
={"net_id": network_id
}
2238 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2239 WHERE
=where_
, FROM
='of_flows')
2241 bottle
.abort(-result
, content
)
2243 data
={'openflow-rules' : content
}
2244 return format_out(data
)
2246 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
2247 def http_put_openflow_id(network_id
):
2248 '''To make actions over the net. The action is to reinstall the openflow rules
2249 network_id can be 'all'
2251 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2253 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2256 if network_id
=='all':
2259 where_
={"uuid": network_id
}
2260 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
2262 bottle
.abort(-result
, content
)
2266 if net
["type"]!="ptp" and net
["type"]!="data":
2269 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
2271 print "http_put_openflow_id error while launching openflow rules"
2272 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2273 data
={'result' : str(result
)+" nets updates"}
2274 return format_out(data
)
2276 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
2277 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
2278 def http_clear_openflow_rules():
2279 '''To make actions over the net. The action is to delete ALL openflow rules
2281 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2283 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2286 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
2288 print "http_delete_openflow_id error while launching openflow rules"
2289 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2292 data
={'result' : " Clearing openflow rules in process"}
2293 return format_out(data
)
2295 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
2296 def http_get_openflow_ports():
2297 '''Obtain switch ports names of openflow controller
2299 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2300 return format_out(data
)
2307 @bottle.route(url_base
+ '/ports', method
='GET')
2308 def http_get_ports():
2310 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2311 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2312 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2313 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2315 ports
= my
.ovim
.get_ports(columns
=select_
, filter=where_
, limit
=limit_
)
2317 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2318 data
={'ports' : ports
}
2319 return format_out(data
)
2320 except ovim
.ovimException
as e
:
2321 my
.logger
.error(str(e
), exc_info
=True)
2322 bottle
.abort(e
.http_code
, str(e
))
2323 except Exception as e
:
2324 my
.logger
.error(str(e
), exc_info
=True)
2325 bottle
.abort(HTTP_Bad_Request
, str(e
))
2327 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2328 def http_get_port_id(port_id
):
2329 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2331 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2333 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2336 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2337 data
= {'port': ports
[0]}
2338 return format_out(data
)
2339 except ovim
.ovimException
as e
:
2340 my
.logger
.error(str(e
), exc_info
=True)
2341 bottle
.abort(e
.http_code
, str(e
))
2342 except Exception as e
:
2343 my
.logger
.error(str(e
), exc_info
=True)
2344 bottle
.abort(HTTP_Bad_Request
, str(e
))
2346 @bottle.route(url_base
+ '/ports', method
='POST')
2347 def http_post_ports():
2348 '''insert an external port into the database.'''
2349 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2351 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2353 http_content
= format_in( port_new_schema
)
2354 r
= remove_extra_items(http_content
, port_new_schema
)
2355 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2356 change_keys_http2db(http_content
['port'], http2db_port
)
2357 port
=http_content
['port']
2359 port_id
= my
.ovim
.new_port(port
)
2360 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2362 bottle
.abort(HTTP_Internal_Server_Error
, "port '{}' inserted but not found at database".format(port_id
))
2365 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2366 data
= {'port': ports
[0]}
2367 return format_out(data
)
2368 except ovim
.ovimException
as e
:
2369 my
.logger
.error(str(e
), exc_info
=True)
2370 bottle
.abort(e
.http_code
, str(e
))
2371 except Exception as e
:
2372 my
.logger
.error(str(e
), exc_info
=True)
2373 bottle
.abort(HTTP_Bad_Request
, str(e
))
2375 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2376 def http_put_port_id(port_id
):
2377 '''update a port_id into the database.'''
2378 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2380 http_content
= format_in( port_update_schema
)
2381 change_keys_http2db(http_content
['port'], http2db_port
)
2382 port_dict
=http_content
['port']
2384 for k
in ('vlan', 'switch_port', 'mac_address', 'tenant_id'):
2385 if k
in port_dict
and not my
.admin
:
2386 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2389 port_id
= my
.ovim
.edit_port(port_id
, port_dict
, my
.admin
)
2390 ports
= my
.ovim
.get_ports(filter={"uuid": port_id
})
2392 bottle
.abort(HTTP_Internal_Server_Error
, "port '{}' edited but not found at database".format(port_id
))
2395 change_keys_http2db(ports
, http2db_port
, reverse
=True)
2396 data
= {'port': ports
[0]}
2397 return format_out(data
)
2398 except ovim
.ovimException
as e
:
2399 my
.logger
.error(str(e
), exc_info
=True)
2400 bottle
.abort(e
.http_code
, str(e
))
2401 except Exception as e
:
2402 my
.logger
.error(str(e
), exc_info
=True)
2403 bottle
.abort(HTTP_Bad_Request
, str(e
))
2406 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2407 def http_delete_port_id(port_id
):
2408 '''delete a port_id from the database.'''
2409 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2411 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2414 result
= my
.ovim
.delete_port(port_id
)
2415 data
= {'result': result
}
2416 return format_out(data
)
2417 except ovim
.ovimException
as e
:
2418 my
.logger
.error(str(e
), exc_info
=True)
2419 bottle
.abort(e
.http_code
, str(e
))
2420 except Exception as e
:
2421 my
.logger
.error(str(e
), exc_info
=True)
2422 bottle
.abort(HTTP_Bad_Request
, str(e
))