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
55 global RADclass_module
56 RADclass
=None #RADclass module is charged only if not in test mode
60 HTTP_Bad_Request
= 400
61 HTTP_Unauthorized
= 401
64 HTTP_Method_Not_Allowed
= 405
65 HTTP_Not_Acceptable
= 406
66 HTTP_Request_Timeout
= 408
68 HTTP_Service_Unavailable
= 503
69 HTTP_Internal_Server_Error
= 500
72 hash_md5
= hashlib
.md5()
73 with
open(fname
, "rb") as f
:
74 for chunk
in iter(lambda: f
.read(4096), b
""):
75 hash_md5
.update(chunk
)
76 return hash_md5
.hexdigest()
78 def md5_string(fname
):
79 hash_md5
= hashlib
.md5()
80 hash_md5
.update(fname
)
81 return hash_md5
.hexdigest()
83 def check_extended(extended
, allow_net_attach
=False):
84 '''Makes and extra checking of extended input that cannot be done using jsonschema
86 allow_net_attach: for allowing or not the uuid field at interfaces
87 that are allowed for instance, but not for flavors
88 Return: (<0, error_text) if error; (0,None) if not error '''
89 if "numas" not in extended
: return 0, None
92 for numa
in extended
["numas"]:
96 if "cores-id" in numa
:
97 if len(numa
["cores-id"]) != numa
["cores"]:
98 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
99 id_s
.extend(numa
["cores-id"])
100 if "threads" in numa
:
102 if "threads-id" in numa
:
103 if len(numa
["threads-id"]) != numa
["threads"]:
104 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
105 id_s
.extend(numa
["threads-id"])
106 if "paired-threads" in numa
:
108 if "paired-threads-id" in numa
:
109 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
110 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
)
111 for pair
in numa
["paired-threads-id"]:
113 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
116 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
118 if "interfaces" in numa
:
122 for interface
in numa
["interfaces"]:
123 if "uuid" in interface
and not allow_net_attach
:
124 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
125 if "mac_address" in interface
and interface
["dedicated"]=="yes":
126 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
)
127 if "name" in interface
:
128 if interface
["name"] in names
:
129 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
130 names
.append(interface
["name"])
131 if "vpci" in interface
:
132 if interface
["vpci"] in vpcis
:
133 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
134 vpcis
.append(interface
["vpci"])
138 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
139 for a
in range(0,len(id_s
)):
141 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
146 # dictionaries that change from HTTP API to database naming
148 http2db_host
={'id':'uuid'}
149 http2db_tenant
={'id':'uuid'}
150 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
151 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
152 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
153 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
154 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'}
156 def remove_extra_items(data
, schema
):
158 if type(data
) is tuple or type(data
) is list:
160 a
= remove_extra_items(d
, schema
['items'])
161 if a
is not None: deleted
.append(a
)
162 elif type(data
) is dict:
163 for k
in data
.keys():
164 if 'properties' not in schema
or k
not in schema
['properties'].keys():
168 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
169 if a
is not None: deleted
.append({k
:a
})
170 if len(deleted
) == 0: return None
171 elif len(deleted
) == 1: return deleted
[0]
174 def delete_nulls(var
):
175 if type(var
) is dict:
177 if var
[k
] is None: del var
[k
]
178 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
179 if delete_nulls(var
[k
]): del var
[k
]
180 if len(var
) == 0: return True
181 elif type(var
) is list or type(var
) is tuple:
183 if type(k
) is dict: delete_nulls(k
)
184 if len(var
) == 0: return True
188 class httpserver(threading
.Thread
):
189 def __init__(self
, db_conn
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
191 Creates a new thread to attend the http connections
193 db_conn: database connection
194 name: name of this thread
195 host: ip or name where to listen
196 port: port where to listen
197 admin: if this has privileges of administrator or not
198 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
204 if config_
is not None:
206 if 'http_threads' not in config_dic
:
207 config_dic
['http_threads'] = {}
208 threading
.Thread
.__init
__(self
)
213 if name
in config_dic
:
214 print "httpserver Warning!!! Onether thread with the same name", name
216 while name
+str(n
) in config_dic
:
220 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
221 config_dic
['http_threads'][name
] = self
223 #Ensure that when the main program exits the thread will also exit
228 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
230 def gethost(self
, host_id
):
231 result
, content
= self
.db
.get_host(host_id
)
233 print "httpserver.gethost error %d %s" % (result
, content
)
234 bottle
.abort(-result
, content
)
236 print "httpserver.gethost host '%s' not found" % host_id
237 bottle
.abort(HTTP_Not_Found
, content
)
239 data
={'host' : content
}
240 convert_boolean(content
, ('admin_state_up',) )
241 change_keys_http2db(content
, http2db_host
, reverse
=True)
243 return format_out(data
)
245 @bottle.route(url_base
+ '/', method
='GET')
248 return 'works' #TODO: put links or redirection to /openvim???
254 def change_keys_http2db(data
, http_db
, reverse
=False):
255 '''Change keys of dictionary data according to the key_dict values
256 This allow change from http interface names to database names.
257 When reverse is True, the change is otherwise
259 data: can be a dictionary or a list
260 http_db: is a dictionary with hhtp names as keys and database names as value
261 reverse: by default change is done from http API to database. If True change is done otherwise
262 Return: None, but data is modified'''
263 if type(data
) is tuple or type(data
) is list:
265 change_keys_http2db(d
, http_db
, reverse
)
266 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
268 for k
,v
in http_db
.items():
269 if v
in data
: data
[k
]=data
.pop(v
)
271 for k
,v
in http_db
.items():
272 if k
in data
: data
[v
]=data
.pop(k
)
276 def format_out(data
):
277 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
278 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
279 bottle
.response
.content_type
='application/yaml'
280 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='"'
281 else: #by default json
282 bottle
.response
.content_type
='application/json'
283 #return data #json no style
284 return json
.dumps(data
, indent
=4) + "\n"
286 def format_in(schema
):
288 error_text
= "Invalid header format "
289 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
290 if 'application/json' in format_type
:
291 error_text
= "Invalid json format "
292 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
293 client_data
= json
.load(bottle
.request
.body
)
294 #client_data = bottle.request.json()
295 elif 'application/yaml' in format_type
:
296 error_text
= "Invalid yaml format "
297 client_data
= yaml
.load(bottle
.request
.body
)
298 elif format_type
== 'application/xml':
299 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
301 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
302 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
304 #if client_data == None:
305 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
309 #print "HTTP input data: ", str(client_data)
310 error_text
= "Invalid content "
311 js_v(client_data
, schema
)
314 except (ValueError, yaml
.YAMLError
) as exc
:
315 error_text
+= str(exc
)
317 bottle
.abort(HTTP_Bad_Request
, error_text
)
318 except js_e
.ValidationError
as exc
:
319 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
320 print " CONTENT: " + str(bottle
.request
.body
.readlines())
322 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
323 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
325 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
328 def filter_query_string(qs
, http2db
, allowed
):
329 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
331 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
332 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
333 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
334 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
335 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
336 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
337 limit: limit dictated by user with the query string 'limit'. 100 by default
338 abort if not permitted, using bottel.abort
343 if type(qs
) is not bottle
.FormsDict
:
344 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
345 #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
349 select
+= qs
.getall(k
)
352 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
357 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
360 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
361 if qs
[k
]!="null": where
[k
]=qs
[k
]
363 if len(select
)==0: select
+= allowed
364 #change from http api to database naming
365 for i
in range(0,len(select
)):
368 select
[i
] = http2db
[k
]
369 change_keys_http2db(where
, http2db
)
370 #print "filter_query_string", select,where,limit
372 return select
,where
,limit
375 def convert_bandwidth(data
, reverse
=False):
376 '''Check the field bandwidth recursively and when found, it removes units and convert to number
377 It assumes that bandwidth is well formed
379 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
380 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
384 if type(data
) is dict:
385 for k
in data
.keys():
386 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
387 convert_bandwidth(data
[k
], reverse
)
388 if "bandwidth" in data
:
390 value
=str(data
["bandwidth"])
392 pos
= value
.find("bps")
394 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
395 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
396 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
398 value
= int(data
["bandwidth"])
399 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
400 else: data
["bandwidth"]=str(value
) + " Mbps"
402 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
404 if type(data
) is tuple or type(data
) is list:
406 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
407 convert_bandwidth(k
, reverse
)
409 def convert_boolean(data
, items
):
410 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
411 It assumes that bandwidth is well formed
413 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
414 'items': tuple of keys to convert
418 if type(data
) is dict:
419 for k
in data
.keys():
420 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
421 convert_boolean(data
[k
], items
)
423 if type(data
[k
]) is str:
424 if data
[k
]=="false": data
[k
]=False
425 elif data
[k
]=="true": data
[k
]=True
426 if type(data
) is tuple or type(data
) is list:
428 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
429 convert_boolean(k
, items
)
431 def convert_datetime2str(var
):
432 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
433 It enters recursively in the dict var finding this kind of variables
435 if type(var
) is dict:
436 for k
,v
in var
.items():
437 if type(v
) is datetime
.datetime
:
438 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
439 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
440 convert_datetime2str(v
)
441 if len(var
) == 0: return True
442 elif type(var
) is list or type(var
) is tuple:
444 convert_datetime2str(v
)
446 def check_valid_tenant(my
, tenant_id
):
449 return HTTP_Unauthorized
, "Needed admin privileges"
451 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
453 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
456 def check_valid_uuid(uuid
):
457 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
459 js_v(uuid
, id_schema
)
461 except js_e
.ValidationError
:
467 Check if string value is a well-wormed url
468 :param url: string url
469 :return: True if is a valid url, False if is not well-formed
472 parsed_url
= urlparse
.urlparse(url
)
487 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
490 @bottle.hook('after_request')
492 #TODO: Alf: Is it needed??
493 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
499 @bottle.route(url_base
+ '/hosts', method
='GET')
500 def http_get_hosts():
501 return format_out(get_hosts())
505 select_
, where_
, limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
506 ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name'))
508 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
509 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
511 print "http_get_hosts Error", content
512 bottle
.abort(-result
, content
)
514 convert_boolean(content
, ('admin_state_up',) )
515 change_keys_http2db(content
, http2db_host
, reverse
=True)
517 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
518 data
={'hosts' : content
}
521 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
522 def http_get_host_id(host_id
):
523 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
524 return my
.gethost(host_id
)
526 @bottle.route(url_base
+ '/hosts', method
='POST')
527 def http_post_hosts():
528 '''insert a host into the database. All resources are got and inserted'''
529 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
532 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
535 http_content
= format_in( host_new_schema
)
536 r
= remove_extra_items(http_content
, host_new_schema
)
537 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
538 change_keys_http2db(http_content
['host'], http2db_host
)
540 host
= http_content
['host']
542 if 'host-data' in http_content
:
543 host
.update(http_content
['host-data'])
544 ip_name
=http_content
['host-data']['ip_name']
545 user
=http_content
['host-data']['user']
546 password
=http_content
['host-data'].get('password', None)
548 ip_name
=host
['ip_name']
550 password
=host
.get('password', None)
551 if not RADclass_module
:
553 RADclass_module
= imp
.find_module("RADclass")
554 except (IOError, ImportError) as e
:
555 raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e
))
558 rad
= RADclass_module
.RADclass()
559 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
562 if not return_status
:
563 print 'http_post_hosts ERROR obtaining RAD', code
564 bottle
.abort(HTTP_Bad_Request
, code
)
567 rad_structure
= yaml
.load(rad
.to_text())
568 print 'rad_structure\n---------------------'
569 print json
.dumps(rad_structure
, indent
=4)
570 print '---------------------'
572 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
573 result
, content
= my
.db
.get_table(FROM
='host_ranking',
577 host
['ranking'] = content
[0]['ranking']
579 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
580 #bottle.abort(HTTP_Bad_Request, error_text)
582 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
583 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
585 features
= rad_structure
['processor'].get('features', ())
586 host
['features'] = ",".join(features
)
589 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
594 for core
in node
['cpu']['eligible_cores']:
595 eligible_cores
.extend(core
)
596 for core
in node
['cpu']['cores']:
597 for thread_id
in core
:
598 c
={'core_id': count
, 'thread_id': thread_id
}
599 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
604 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
605 if port_v
['virtual']:
609 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
610 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
611 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
613 #sort sriov according to pci and rename them to the vf number
614 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
616 for sriov
in new_sriovs
:
617 sriov
['source_name'] = index
619 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
620 memory
=node
['memory']['node_size'] / (1024*1024*1024)
621 #memory=get_next_2pow(node['memory']['hugepage_nr'])
622 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
623 print json
.dumps(host
, indent
=4)
627 result
, content
= my
.db
.new_host(host
)
629 if content
['admin_state_up']:
631 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
632 host_develop_mode
= True if config_dic
['mode']=='development' else False
633 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
634 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'],
635 test
=host_test_mode
, image_path
=config_dic
['image_path'],
636 version
=config_dic
['version'], host_id
=content
['uuid'],
637 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
639 config_dic
['host_threads'][ content
['uuid'] ] = thread
641 if config_dic
['network_type'] == 'ovs':
643 create_dhcp_ovs_bridge()
644 config_dic
['host_threads'][content
['uuid']].insert_task("new-ovsbridge")
645 # check if more host exist
646 create_vxlan_mesh(content
['uuid'])
649 change_keys_http2db(content
, http2db_host
, reverse
=True)
650 if len(warning_text
)>0:
651 content
["warning"]= warning_text
652 data
={'host' : content
}
653 return format_out(data
)
655 bottle
.abort(HTTP_Bad_Request
, content
)
659 def get_dhcp_controller():
661 Create an host_thread object for manage openvim controller and not create a thread for itself
662 :return: dhcp_host openvim controller object
665 if 'openvim_controller' in config_dic
['host_threads']:
666 return config_dic
['host_threads']['openvim_controller']
669 controller_ip
= config_dic
['ovs_controller_ip']
670 ovs_controller_user
= config_dic
['ovs_controller_user']
672 host_test_mode
= True if config_dic
['mode'] == 'test' or config_dic
['mode'] == "OF only" else False
673 host_develop_mode
= True if config_dic
['mode'] == 'development' else False
675 dhcp_host
= ht
.host_thread(name
='openvim_controller', user
=ovs_controller_user
, host
=controller_ip
, db
=config_dic
['db'],
676 db_lock
=config_dic
['db_lock'], test
=host_test_mode
,
677 image_path
=config_dic
['image_path'], version
=config_dic
['version'],
678 host_id
='openvim_controller', develop_mode
=host_develop_mode
,
679 develop_bridge_iface
=bridge_ifaces
)
681 config_dic
['host_threads']['openvim_controller'] = dhcp_host
682 dhcp_host
.ssh_connect()
686 def delete_dhcp_ovs_bridge(vlan
, net_uuid
):
688 Delete bridges and port created during dhcp launching at openvim controller
689 :param vlan: net vlan id
690 :param net_uuid: network identifier
693 dhcp_path
= config_dic
['ovs_controller_file_path']
695 controller_host
= get_dhcp_controller()
696 controller_host
.delete_dhcp_port(vlan
, net_uuid
)
697 controller_host
.delete_dhcp_server(vlan
, net_uuid
, dhcp_path
)
700 def create_dhcp_ovs_bridge():
702 Initialize bridge to allocate the dhcp server at openvim controller
705 controller_host
= get_dhcp_controller()
706 controller_host
.create_ovs_bridge()
709 def set_mac_dhcp(vm_ip
, vlan
, first_ip
, last_ip
, cidr
, mac
):
711 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
712 :param vm_ip: IP address asigned to a VM
713 :param vlan: Segmentation id
714 :param first_ip: First dhcp range ip
715 :param last_ip: Last dhcp range ip
716 :param cidr: net cidr
717 :param mac: VM vnic mac to be macthed with the IP received
721 ip_tools
= IPNetwork(cidr
)
722 cidr_len
= ip_tools
.prefixlen
723 dhcp_netmask
= str(ip_tools
.netmask
)
724 dhcp_path
= config_dic
['ovs_controller_file_path']
726 new_cidr
= [first_ip
+ '/' + str(cidr_len
)]
727 if not len(all_matching_cidrs(vm_ip
, new_cidr
)):
730 controller_host
= get_dhcp_controller()
731 controller_host
.set_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_netmask
, dhcp_path
)
734 def delete_mac_dhcp(vm_ip
, vlan
, mac
):
736 Delete into dhcp conf file the ip assigned to a specific MAC address
737 :param vm_ip: IP address asigned to a VM
738 :param vlan: Segmentation id
739 :param mac: VM vnic mac to be macthed with the IP received
743 dhcp_path
= config_dic
['ovs_controller_file_path']
745 controller_host
= get_dhcp_controller()
746 controller_host
.delete_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_path
)
749 def launch_dhcp_server(vlan
, first_ip
, last_ip
, cidr
):
751 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
752 :param vlan: vlan identifier
753 :param first_ip: First dhcp range ip
754 :param last_ip: Last dhcp range ip
755 :param cidr: net cidr
758 ip_tools
= IPNetwork(cidr
)
759 dhcp_netmask
= str(ip_tools
.netmask
)
760 ip_range
= [first_ip
, last_ip
]
761 dhcp_path
= config_dic
['ovs_controller_file_path']
763 controller_host
= get_dhcp_controller()
764 controller_host
.create_linux_bridge(vlan
)
765 controller_host
.create_dhcp_interfaces(vlan
, first_ip
, dhcp_netmask
)
766 controller_host
.launch_dhcp_server(vlan
, ip_range
, dhcp_netmask
, dhcp_path
)
769 def create_vxlan_mesh(host_id
):
771 Create vxlan mesh across all openvimc controller and computes.
772 :param host_id: host identifier
773 :param host_id: host identifier
776 dhcp_compute_name
= get_vxlan_interface("dhcp")
777 existing_hosts
= get_hosts()
778 if len(existing_hosts
['hosts']) > 0:
779 # vlxan mesh creation between openvim controller and computes
780 computes_available
= existing_hosts
['hosts']
781 controller_host
= get_dhcp_controller()
782 for compute
in computes_available
:
783 vxlan_interface_name
= get_vxlan_interface(compute
['id'][:8])
784 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan", dhcp_compute_name
, controller_host
.host
)
785 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute
['ip_name'])
787 # vlxan mesh creation between openvim computes
788 for count
, compute_owner
in enumerate(computes_available
):
789 for compute
in computes_available
:
790 if compute_owner
['id'] == compute
['id']:
793 vxlan_interface_name
= get_vxlan_interface(compute_owner
['id'][:8])
794 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute_owner
['ip_name'])
795 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan",
796 vxlan_interface_name
,
797 compute_owner
['ip_name'])
800 def delete_vxlan_mesh(host_id
):
802 Create a task for remove a specific compute of the vlxan mesh
803 :param host_id: host id to be deleted.
805 existing_hosts
= get_hosts()
806 computes_available
= existing_hosts
['hosts']
808 vxlan_interface_name
= get_vxlan_interface(host_id
[:8])
809 controller_host
= get_dhcp_controller()
810 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
811 # remove bridge from openvim controller if no more computes exist
812 if len(existing_hosts
):
813 controller_host
.delete_ovs_bridge()
815 for compute
in computes_available
:
816 if host_id
== compute
['id']:
819 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
820 config_dic
['host_threads'][compute
['id']].insert_task("del-vxlan", vxlan_interface_name
)
823 def get_vxlan_interface(local_uuid
):
825 Genearte a vxlan interface name
826 :param local_uuid: host id
827 :return: vlxan-8digits
829 return 'vxlan-' + local_uuid
[:8]
832 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
833 def http_put_host_id(host_id
):
834 '''modify a host into the database. All resources are got and inserted'''
835 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
838 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
841 http_content
= format_in( host_edit_schema
)
842 r
= remove_extra_items(http_content
, host_edit_schema
)
843 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
844 change_keys_http2db(http_content
['host'], http2db_host
)
847 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
849 convert_boolean(content
, ('admin_state_up',) )
850 change_keys_http2db(content
, http2db_host
, reverse
=True)
851 data
={'host' : content
}
853 if config_dic
['network_type'] == 'ovs':
854 delete_vxlan_mesh(host_id
)
855 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
858 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
859 config_dic
['host_threads'][host_id
].user
= content
['user']
860 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
861 config_dic
['host_threads'][host_id
].insert_task("reload")
863 if config_dic
['network_type'] == 'ovs':
864 # create mesh with new host data
865 config_dic
['host_threads'][host_id
].insert_task("new-ovsbridge")
866 create_vxlan_mesh(host_id
)
869 return format_out(data
)
871 bottle
.abort(HTTP_Bad_Request
, content
)
876 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
877 def http_delete_host_id(host_id
):
878 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
881 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
882 result
, content
= my
.db
.delete_row('hosts', host_id
)
884 bottle
.abort(HTTP_Not_Found
, content
)
886 if config_dic
['network_type'] == 'ovs':
887 delete_vxlan_mesh(host_id
)
889 if host_id
in config_dic
['host_threads']:
890 if config_dic
['network_type'] == 'ovs':
891 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
892 config_dic
['host_threads'][host_id
].insert_task("exit")
894 data
={'result' : content
}
895 return format_out(data
)
897 print "http_delete_host_id error",result
, content
898 bottle
.abort(-result
, content
)
907 @bottle.route(url_base
+ '/tenants', method
='GET')
908 def http_get_tenants():
909 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
910 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
911 ('id','name','description','enabled') )
912 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
914 print "http_get_tenants Error", content
915 bottle
.abort(-result
, content
)
917 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
918 convert_boolean(content
, ('enabled',))
919 data
={'tenants' : content
}
920 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
921 return format_out(data
)
923 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
924 def http_get_tenant_id(tenant_id
):
925 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
926 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
928 print "http_get_tenant_id error %d %s" % (result
, content
)
929 bottle
.abort(-result
, content
)
931 print "http_get_tenant_id tenant '%s' not found" % tenant_id
932 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
934 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
935 convert_boolean(content
, ('enabled',))
936 data
={'tenant' : content
[0]}
937 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
938 return format_out(data
)
941 @bottle.route(url_base
+ '/tenants', method
='POST')
942 def http_post_tenants():
943 '''insert a tenant into the database.'''
944 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
946 http_content
= format_in( tenant_new_schema
)
947 r
= remove_extra_items(http_content
, tenant_new_schema
)
948 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
949 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
952 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
955 return http_get_tenant_id(content
)
957 bottle
.abort(-result
, content
)
960 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
961 def http_put_tenant_id(tenant_id
):
962 '''update a tenant into the database.'''
963 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
965 http_content
= format_in( tenant_edit_schema
)
966 r
= remove_extra_items(http_content
, tenant_edit_schema
)
967 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
968 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
971 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
973 return http_get_tenant_id(tenant_id
)
975 bottle
.abort(-result
, content
)
978 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
979 def http_delete_tenant_id(tenant_id
):
980 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
982 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
985 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
988 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
990 bottle
.abort(HTTP_Not_Found
, content
)
992 print "alf", tenants_flavors
, tenants_images
993 for flavor
in tenants_flavors
:
994 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
995 for image
in tenants_images
:
996 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
997 data
={'result' : content
}
998 return format_out(data
)
1000 print "http_delete_tenant_id error",result
, content
1001 bottle
.abort(-result
, content
)
1008 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
1009 def http_get_flavors(tenant_id
):
1010 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1011 #check valid tenant_id
1012 result
,content
= check_valid_tenant(my
, tenant_id
)
1014 bottle
.abort(result
, content
)
1016 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1017 ('id','name','description','public') )
1018 if tenant_id
=='any':
1021 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
1022 where_
['tenant_id'] = tenant_id
1023 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
1025 print "http_get_flavors Error", content
1026 bottle
.abort(-result
, content
)
1028 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1030 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
1031 data
={'flavors' : content
}
1032 return format_out(data
)
1034 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
1035 def http_get_flavor_id(tenant_id
, flavor_id
):
1036 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1037 #check valid tenant_id
1038 result
,content
= check_valid_tenant(my
, tenant_id
)
1040 bottle
.abort(result
, content
)
1042 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1043 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1044 if tenant_id
=='any':
1047 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1048 where_
['tenant_id'] = tenant_id
1049 where_
['uuid'] = flavor_id
1050 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1053 print "http_get_flavor_id error %d %s" % (result
, content
)
1054 bottle
.abort(-result
, content
)
1056 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
1057 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
1059 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1060 if 'extended' in content
[0] and content
[0]['extended'] is not None:
1061 extended
= json
.loads(content
[0]['extended'])
1062 if 'devices' in extended
:
1063 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
1064 content
[0]['extended']=extended
1065 convert_bandwidth(content
[0], reverse
=True)
1066 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1067 data
={'flavor' : content
[0]}
1068 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1069 return format_out(data
)
1072 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
1073 def http_post_flavors(tenant_id
):
1074 '''insert a flavor into the database, and attach to tenant.'''
1075 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1076 #check valid tenant_id
1077 result
,content
= check_valid_tenant(my
, tenant_id
)
1079 bottle
.abort(result
, content
)
1080 http_content
= format_in( flavor_new_schema
)
1081 r
= remove_extra_items(http_content
, flavor_new_schema
)
1082 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
1083 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1084 extended_dict
= http_content
['flavor'].pop('extended', None)
1085 if extended_dict
is not None:
1086 result
, content
= check_extended(extended_dict
)
1088 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
1089 bottle
.abort(-result
, content
)
1091 convert_bandwidth(extended_dict
)
1092 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1093 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1094 #insert in data base
1095 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
1097 return http_get_flavor_id(tenant_id
, content
)
1099 print "http_psot_flavors error %d %s" % (result
, content
)
1100 bottle
.abort(-result
, content
)
1103 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
1104 def http_delete_flavor_id(tenant_id
, flavor_id
):
1105 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1106 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1107 #check valid tenant_id
1108 result
,content
= check_valid_tenant(my
, tenant_id
)
1110 bottle
.abort(result
, content
)
1112 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
1114 bottle
.abort(HTTP_Not_Found
, content
)
1116 data
={'result' : content
}
1117 return format_out(data
)
1119 print "http_delete_flavor_id error",result
, content
1120 bottle
.abort(-result
, content
)
1123 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
1124 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
1125 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1126 #TODO alf: not tested at all!!!
1127 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1128 #check valid tenant_id
1129 result
,content
= check_valid_tenant(my
, tenant_id
)
1131 bottle
.abort(result
, content
)
1132 if tenant_id
=='any':
1133 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1135 if action
!='attach' and action
!= 'detach':
1136 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1139 #Ensure that flavor exist
1140 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1141 where_
={'uuid': flavor_id
}
1142 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1144 if action
=='attach':
1145 text_error
="Flavor '%s' not found" % flavor_id
1147 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
1148 bottle
.abort(HTTP_Not_Found
, text_error
)
1152 if action
=='attach':
1153 if flavor
['tenant_id']!=None:
1154 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
1155 if flavor
['public']=='no' and not my
.admin
:
1156 #allow only attaching public flavors
1157 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
1159 #insert in data base
1160 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
1162 return http_get_flavor_id(tenant_id
, flavor_id
)
1164 if flavor
['tenant_id']==None:
1165 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
1166 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
1168 if flavor
['public']=='no':
1169 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1170 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
1171 data
={'result' : "flavor detached"}
1172 return format_out(data
)
1174 #if get here is because an error
1175 print "http_attach_detach_flavors error %d %s" % (result
, content
)
1176 bottle
.abort(-result
, content
)
1179 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
1180 def http_put_flavor_id(tenant_id
, flavor_id
):
1181 '''update a flavor_id into the database.'''
1182 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1183 #check valid tenant_id
1184 result
,content
= check_valid_tenant(my
, tenant_id
)
1186 bottle
.abort(result
, content
)
1188 http_content
= format_in( flavor_update_schema
)
1189 r
= remove_extra_items(http_content
, flavor_update_schema
)
1190 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1191 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1192 extended_dict
= http_content
['flavor'].pop('extended', None)
1193 if extended_dict
is not None:
1194 result
, content
= check_extended(extended_dict
)
1196 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
1197 bottle
.abort(-result
, content
)
1199 convert_bandwidth(extended_dict
)
1200 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1201 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1202 #Ensure that flavor exist
1203 where_
={'uuid': flavor_id
}
1204 if tenant_id
=='any':
1207 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1208 where_
['tenant_id'] = tenant_id
1209 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1211 text_error
="Flavor '%s' not found" % flavor_id
1212 if tenant_id
!='any':
1213 text_error
+=" for tenant '%s'" % flavor_id
1214 bottle
.abort(HTTP_Not_Found
, text_error
)
1217 if content
[0]['public']=='yes' and not my
.admin
:
1218 #allow only modifications over private flavors
1219 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1221 #insert in data base
1222 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1225 print "http_put_flavor_id error %d %s" % (result
, content
)
1226 bottle
.abort(-result
, content
)
1229 return http_get_flavor_id(tenant_id
, flavor_id
)
1237 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1238 def http_get_images(tenant_id
):
1239 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1240 #check valid tenant_id
1241 result
,content
= check_valid_tenant(my
, tenant_id
)
1243 bottle
.abort(result
, content
)
1245 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1246 ('id','name','checksum','description','path','public') )
1247 if tenant_id
=='any':
1251 from_
='tenants_images right join images on tenants_images.image_id=images.uuid'
1252 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1253 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1255 print "http_get_images Error", content
1256 bottle
.abort(-result
, content
)
1258 change_keys_http2db(content
, http2db_image
, reverse
=True)
1259 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1260 data
={'images' : content
}
1261 return format_out(data
)
1263 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1264 def http_get_image_id(tenant_id
, image_id
):
1265 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1266 #check valid tenant_id
1267 result
,content
= check_valid_tenant(my
, tenant_id
)
1269 bottle
.abort(result
, content
)
1271 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1272 ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
1273 if tenant_id
=='any':
1277 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1278 where_or_
= {'tenant_id': tenant_id
, 'public': "yes"}
1279 where_
['uuid'] = image_id
1280 result
, content
= my
.db
.get_table(SELECT
=select_
, DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1283 print "http_get_images error %d %s" % (result
, content
)
1284 bottle
.abort(-result
, content
)
1286 print "http_get_images image '%s' not found" % str(image_id
)
1287 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1289 convert_datetime2str(content
)
1290 change_keys_http2db(content
, http2db_image
, reverse
=True)
1291 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1292 metadata
= json
.loads(content
[0]['metadata'])
1293 content
[0]['metadata']=metadata
1294 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1295 data
={'image' : content
[0]}
1296 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1297 return format_out(data
)
1299 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1300 def http_post_images(tenant_id
):
1301 '''insert a image into the database, and attach to tenant.'''
1302 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1303 #check valid tenant_id
1304 result
,content
= check_valid_tenant(my
, tenant_id
)
1306 bottle
.abort(result
, content
)
1307 http_content
= format_in(image_new_schema
)
1308 r
= remove_extra_items(http_content
, image_new_schema
)
1309 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1310 change_keys_http2db(http_content
['image'], http2db_image
)
1311 metadata_dict
= http_content
['image'].pop('metadata', None)
1312 if metadata_dict
is not None:
1313 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1316 image_file
= http_content
['image'].get('path',None)
1317 parsed_url
= urlparse
.urlparse(image_file
)
1318 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "":
1319 # The path is a local file
1320 if os
.path
.exists(image_file
):
1321 http_content
['image']['checksum'] = md5(image_file
)
1323 # The path is a URL. Code should be added to download the image and calculate the checksum
1324 #http_content['image']['checksum'] = md5(downloaded_image)
1326 # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
1327 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1329 if 'checksum' not in http_content
['image']:
1330 http_content
['image']['checksum'] = md5_string(image_file
)
1332 # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
1333 # If it is a URL, no error is sent. Checksum will be an empty string
1334 if parsed_url
.scheme
== "" and parsed_url
.netloc
== "" and 'checksum' not in http_content
['image']:
1335 content
= "Image file not found"
1336 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1337 bottle
.abort(HTTP_Bad_Request
, content
)
1338 except Exception as e
:
1339 print "ERROR. Unexpected exception: %s" % (str(e
))
1340 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1341 #insert in data base
1342 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1344 return http_get_image_id(tenant_id
, content
)
1346 print "http_post_images error %d %s" % (result
, content
)
1347 bottle
.abort(-result
, content
)
1350 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1351 def http_delete_image_id(tenant_id
, image_id
):
1352 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1353 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1354 #check valid tenant_id
1355 result
,content
= check_valid_tenant(my
, tenant_id
)
1357 bottle
.abort(result
, content
)
1358 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1360 bottle
.abort(HTTP_Not_Found
, content
)
1362 data
={'result' : content
}
1363 return format_out(data
)
1365 print "http_delete_image_id error",result
, content
1366 bottle
.abort(-result
, content
)
1369 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1370 def http_attach_detach_images(tenant_id
, image_id
, action
):
1371 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1372 #TODO alf: not tested at all!!!
1373 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1374 #check valid tenant_id
1375 result
,content
= check_valid_tenant(my
, tenant_id
)
1377 bottle
.abort(result
, content
)
1378 if tenant_id
=='any':
1379 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1381 if action
!='attach' and action
!= 'detach':
1382 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1385 #Ensure that image exist
1386 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1387 where_
={'uuid': image_id
}
1388 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1390 if action
=='attach':
1391 text_error
="Image '%s' not found" % image_id
1393 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1394 bottle
.abort(HTTP_Not_Found
, text_error
)
1398 if action
=='attach':
1399 if image
['tenant_id']!=None:
1400 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1401 if image
['public']=='no' and not my
.admin
:
1402 #allow only attaching public images
1403 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1405 #insert in data base
1406 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1408 return http_get_image_id(tenant_id
, image_id
)
1410 if image
['tenant_id']==None:
1411 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1412 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1414 if image
['public']=='no':
1415 #try to delete the image completely to avoid orphan images, IGNORE error
1416 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1417 data
={'result' : "image detached"}
1418 return format_out(data
)
1420 #if get here is because an error
1421 print "http_attach_detach_images error %d %s" % (result
, content
)
1422 bottle
.abort(-result
, content
)
1425 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1426 def http_put_image_id(tenant_id
, image_id
):
1427 '''update a image_id into the database.'''
1428 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1429 #check valid tenant_id
1430 result
,content
= check_valid_tenant(my
, tenant_id
)
1432 bottle
.abort(result
, content
)
1434 http_content
= format_in( image_update_schema
)
1435 r
= remove_extra_items(http_content
, image_update_schema
)
1436 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1437 change_keys_http2db(http_content
['image'], http2db_image
)
1438 metadata_dict
= http_content
['image'].pop('metadata', None)
1439 if metadata_dict
is not None:
1440 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1441 #Ensure that image exist
1442 where_
={'uuid': image_id
}
1443 if tenant_id
=='any':
1447 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1448 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1449 result
, content
= my
.db
.get_table(SELECT
=('public',), DISTINCT
=True, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND")
1451 text_error
="Image '%s' not found" % image_id
1452 if tenant_id
!='any':
1453 text_error
+=" for tenant '%s'" % image_id
1454 bottle
.abort(HTTP_Not_Found
, text_error
)
1457 if content
[0]['public']=='yes' and not my
.admin
:
1458 #allow only modifications over private images
1459 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1461 #insert in data base
1462 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1465 print "http_put_image_id error %d %s" % (result
, content
)
1466 bottle
.abort(-result
, content
)
1469 return http_get_image_id(tenant_id
, image_id
)
1476 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1477 def http_get_servers(tenant_id
):
1478 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1479 result
,content
= check_valid_tenant(my
, tenant_id
)
1481 bottle
.abort(result
, content
)
1484 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1485 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1486 if tenant_id
!='any':
1487 where_
['tenant_id'] = tenant_id
1488 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1490 print "http_get_servers Error", content
1491 bottle
.abort(-result
, content
)
1493 change_keys_http2db(content
, http2db_server
, reverse
=True)
1495 tenant_id
= row
.pop('tenant_id')
1496 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1497 data
={'servers' : content
}
1498 return format_out(data
)
1500 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1501 def http_get_server_id(tenant_id
, server_id
):
1502 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1503 #check valid tenant_id
1504 result
,content
= check_valid_tenant(my
, tenant_id
)
1506 bottle
.abort(result
, content
)
1509 result
, content
= my
.db
.get_instance(server_id
)
1511 bottle
.abort(HTTP_Not_Found
, content
)
1513 #change image/flavor-id to id and link
1514 convert_bandwidth(content
, reverse
=True)
1515 convert_datetime2str(content
)
1516 if content
["ram"]==0 : del content
["ram"]
1517 if content
["vcpus"]==0 : del content
["vcpus"]
1518 if 'flavor_id' in content
:
1519 if content
['flavor_id'] is not None:
1520 content
['flavor'] = {'id':content
['flavor_id'],
1521 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1523 del content
['flavor_id']
1524 if 'image_id' in content
:
1525 if content
['image_id'] is not None:
1526 content
['image'] = {'id':content
['image_id'],
1527 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1529 del content
['image_id']
1530 change_keys_http2db(content
, http2db_server
, reverse
=True)
1531 if 'extended' in content
:
1532 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1534 data
={'server' : content
}
1535 return format_out(data
)
1537 bottle
.abort(-result
, content
)
1540 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1541 def http_post_server_id(tenant_id
):
1542 '''deploys a new server'''
1543 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1544 #check valid tenant_id
1545 result
,content
= check_valid_tenant(my
, tenant_id
)
1547 bottle
.abort(result
, content
)
1549 if tenant_id
=='any':
1550 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1552 http_content
= format_in( server_new_schema
)
1553 r
= remove_extra_items(http_content
, server_new_schema
)
1554 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1555 change_keys_http2db(http_content
['server'], http2db_server
)
1556 extended_dict
= http_content
['server'].get('extended', None)
1557 if extended_dict
is not None:
1558 result
, content
= check_extended(extended_dict
, True)
1560 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1561 bottle
.abort(-result
, content
)
1563 convert_bandwidth(extended_dict
)
1564 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1566 server
= http_content
['server']
1567 server_start
= server
.get('start', 'yes')
1568 server
['tenant_id'] = tenant_id
1569 #check flavor valid and take info
1570 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1571 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1573 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1575 server
['flavor']=content
[0]
1576 #check image valid and take info
1577 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1578 SELECT
=('path', 'metadata', 'image_id'),
1579 WHERE
={'uuid':server
['image_id'], "status":"ACTIVE"},
1580 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'},
1584 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1586 for image_dict
in content
:
1587 if image_dict
.get("image_id"):
1590 # insert in data base tenants_images
1591 r2
, c2
= my
.db
.new_row('tenants_images', {'image_id': server
['image_id'], 'tenant_id': tenant_id
})
1593 bottle
.abort(HTTP_Not_Found
, 'image_id %s cannot be used. Error %s' % (server
['image_id'], c2
))
1595 server
['image']={"path": content
[0]["path"], "metadata": content
[0]["metadata"]}
1596 if "hosts_id" in server
:
1597 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1599 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1601 #print json.dumps(server, indent=4)
1603 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1606 #Insert instance to database
1609 print "inserting at DB"
1611 if server_start
== 'no':
1612 content
['status'] = 'INACTIVE'
1614 for net
in http_content
['server']['networks']:
1615 if net
['type'] == 'instance:ovs':
1616 dhcp_nets_id
.append(get_network_id(net
['net_id']))
1619 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1620 if new_instance_result
< 0:
1621 print "Error http_post_servers() :", new_instance_result
, new_instance
1622 bottle
.abort(-new_instance_result
, new_instance
)
1625 print "inserted at DB"
1627 for port
in ports_to_free
:
1628 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1630 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1633 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1635 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1638 #look for dhcp ip address
1639 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "ip_address", "net_id"], WHERE
={"instance_id": new_instance
})
1642 if config_dic
.get("dhcp_server") and iface
["net_id"] in config_dic
["dhcp_nets"]:
1643 #print "dhcp insert add task"
1644 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1646 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1648 #ensure compute contain the bridge for ovs networks:
1649 server_net
= get_network_id(iface
['net_id'])
1650 if server_net
["network"].get('provider:physical', "")[:3] == 'OVS':
1651 vlan
= str(server_net
['network']['provider:vlan'])
1652 dhcp_enable
= bool(server_net
['network']['enable_dhcp'])
1654 dhcp_firt_ip
= str(server_net
['network']['dhcp_first_ip'])
1655 dhcp_last_ip
= str(server_net
['network']['dhcp_last_ip'])
1656 dhcp_cidr
= str(server_net
['network']['cidr'])
1657 vm_dhcp_ip
= c2
[0]["ip_address"]
1658 config_dic
['host_threads'][server
['host_id']].insert_task("create-ovs-bridge-port", vlan
)
1660 set_mac_dhcp(vm_dhcp_ip
, vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
, c2
[0]['mac'])
1661 launch_dhcp_server(vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
)
1664 server
['uuid'] = new_instance
1665 server_start
= server
.get('start', 'yes')
1667 if server_start
!= 'no':
1668 server
['paused'] = True if server_start
== 'paused' else False
1669 server
['action'] = {"start":None}
1670 server
['status'] = "CREATING"
1672 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1674 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1676 return http_get_server_id(tenant_id
, new_instance
)
1678 bottle
.abort(HTTP_Bad_Request
, content
)
1681 def http_server_action(server_id
, tenant_id
, action
):
1682 '''Perform actions over a server as resume, reboot, terminate, ...'''
1683 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1684 server
={"uuid": server_id
, "action":action
}
1685 where
={'uuid': server_id
}
1686 if tenant_id
!='any':
1687 where
['tenant_id']= tenant_id
1688 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1690 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1693 print "http_post_server_action error getting data %d %s" % (result
, content
)
1694 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1696 server
.update(content
[0])
1697 tenant_id
= server
["tenant_id"]
1699 #TODO check a right content
1701 if 'terminate' in action
:
1702 new_status
='DELETING'
1703 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1704 if 'terminate' not in action
and 'rebuild' not in action
:
1705 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1707 # elif server['status'] == 'INACTIVE':
1708 # if 'start' not in action and 'createImage' not in action:
1709 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1711 # if 'start' in action:
1712 # new_status='CREATING'
1713 # server['paused']='no'
1714 # elif server['status'] == 'PAUSED':
1715 # if 'resume' not in action:
1716 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1718 # elif server['status'] == 'ACTIVE':
1719 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1720 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1723 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1724 #check image valid and take info
1725 image_id
= server
['image_id']
1726 if 'createImage' in action
:
1727 if 'imageRef' in action
['createImage']:
1728 image_id
= action
['createImage']['imageRef']
1729 elif 'disk' in action
['createImage']:
1730 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1731 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1733 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1737 if action
['createImage']['imageRef']['disk'] != None:
1738 for disk
in content
:
1739 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1740 disk_id
= disk
['image_id']
1743 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1746 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1750 image_id
= content
[0]['image_id']
1752 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1753 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, "status":"ACTIVE"},
1754 WHERE_OR
={'tenant_id':tenant_id
, 'public': 'yes'}, WHERE_AND_OR
="AND", DISTINCT
=True)
1756 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1758 if content
[0]['metadata'] is not None:
1760 metadata
= json
.loads(content
[0]['metadata'])
1762 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1763 content
[0]['metadata']=metadata
1765 content
[0]['metadata'] = {}
1766 server
['image']=content
[0]
1767 if 'createImage' in action
:
1768 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1769 if 'createImage' in action
:
1770 #Create an entry in Database for the new image
1771 new_image
={'status':'BUILD', 'progress': 0 }
1772 new_image_metadata
=content
[0]
1773 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1774 new_image_metadata
.update(server
['image']['metadata'])
1775 new_image_metadata
= {"use_incremental":"no"}
1776 if 'metadata' in action
['createImage']:
1777 new_image_metadata
.update(action
['createImage']['metadata'])
1778 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1779 new_image
['name'] = action
['createImage'].get('name', None)
1780 new_image
['description'] = action
['createImage'].get('description', None)
1781 new_image
['uuid']=my
.db
.new_uuid()
1782 if 'path' in action
['createImage']:
1783 new_image
['path'] = action
['createImage']['path']
1785 new_image
['path']="/provisional/path/" + new_image
['uuid']
1786 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1788 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1790 server
['new_image'] = new_image
1794 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1796 print "Task queue full at host ", server
['host_id']
1797 bottle
.abort(HTTP_Request_Timeout
, c
)
1798 if 'createImage' in action
and result
>= 0:
1799 return http_get_image_id(tenant_id
, image_uuid
)
1801 #Update DB only for CREATING or DELETING status
1802 data
={'result' : 'in process'}
1803 if new_status
!= None and new_status
== 'DELETING':
1808 #look for dhcp ip address
1809 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1810 r
, c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, net_ovs_list
, "requested by http")
1811 for port
in ports_to_free
:
1812 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1814 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1815 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1817 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1819 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1820 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1821 #look for dhcp ip address
1822 if r2
>0 and config_dic
.get("dhcp_server"):
1824 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1825 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1826 #print "dhcp insert del task"
1828 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1829 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1830 for net
in net_ovs_list
:
1835 delete_dhcp_ovs_bridge(vlan
, net_id
)
1836 delete_mac_dhcp(vm_ip
, vlan
, mac
)
1837 config_dic
['host_threads'][server
['host_id']].insert_task('del-ovs-port', vlan
, net_id
)
1838 return format_out(data
)
1842 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1843 def http_delete_server_id(tenant_id
, server_id
):
1844 '''delete a server'''
1845 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1846 #check valid tenant_id
1847 result
,content
= check_valid_tenant(my
, tenant_id
)
1849 bottle
.abort(result
, content
)
1852 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1855 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1856 def http_post_server_action(tenant_id
, server_id
):
1857 '''take an action over a server'''
1858 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1859 #check valid tenant_id
1860 result
,content
= check_valid_tenant(my
, tenant_id
)
1862 bottle
.abort(result
, content
)
1864 http_content
= format_in( server_action_schema
)
1865 #r = remove_extra_items(http_content, server_action_schema)
1866 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1868 return http_server_action(server_id
, tenant_id
, http_content
)
1875 @bottle.route(url_base
+ '/networks', method
='GET')
1876 def http_get_networks():
1877 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1879 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1880 ('id','name','tenant_id','type',
1881 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1882 #TODO temporally remove tenant_id
1883 if "tenant_id" in where_
:
1884 del where_
["tenant_id"]
1885 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1887 print "http_get_networks error %d %s" % (result
, content
)
1888 bottle
.abort(-result
, content
)
1890 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1891 delete_nulls(content
)
1892 change_keys_http2db(content
, http2db_network
, reverse
=True)
1893 data
={'networks' : content
}
1894 return format_out(data
)
1896 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1897 def http_get_network_id(network_id
):
1898 data
= get_network_id(network_id
)
1899 return format_out(data
)
1901 def get_network_id(network_id
):
1902 my
= config_dic
['http_threads'][threading
.current_thread().name
]
1904 where_
= bottle
.request
.query
1905 where_
['uuid'] = network_id
1906 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1909 print "http_get_networks_id error %d %s" % (result
, content
)
1910 bottle
.abort(-result
, content
)
1912 print "http_get_networks_id network '%s' not found" % network_id
1913 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1915 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1916 change_keys_http2db(content
, http2db_network
, reverse
=True)
1918 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1919 WHERE
={'net_id': network_id
}, LIMIT
=100)
1921 content
[0]['ports'] = ports
1922 delete_nulls(content
[0])
1923 data
={'network' : content
[0]}
1926 @bottle.route(url_base
+ '/networks', method
='POST')
1927 def http_post_networks():
1928 '''insert a network into the database.'''
1929 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1931 http_content
= format_in( network_new_schema
)
1932 r
= remove_extra_items(http_content
, network_new_schema
)
1933 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1934 change_keys_http2db(http_content
['network'], http2db_network
)
1935 network
=http_content
['network']
1936 #check valid tenant_id
1937 tenant_id
= network
.get('tenant_id')
1939 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1941 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1945 net_provider
= network
.get('provider')
1946 net_type
= network
.get('type')
1947 net_enable_dhcp
= network
.get('enable_dhcp')
1949 net_cidr
= network
.get('cidr')
1951 net_vlan
= network
.get("vlan")
1952 net_bind_net
= network
.get("bind_net")
1953 net_bind_type
= network
.get("bind_type")
1954 name
= network
["name"]
1956 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1957 vlan_index
=name
.rfind(":")
1958 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1960 vlan_tag
= int(name
[vlan_index
+1:])
1961 if vlan_tag
>0 and vlan_tag
< 4096:
1962 net_bind_net
= name
[:vlan_index
]
1963 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1967 if net_bind_net
!= None:
1968 #look for a valid net
1969 if check_valid_uuid(net_bind_net
):
1970 net_bind_key
= "uuid"
1972 net_bind_key
= "name"
1973 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1975 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1978 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1981 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1983 network
["bind_net"] = content
[0]["uuid"]
1984 if net_bind_type
!= None:
1985 if net_bind_type
[0:5] != "vlan:":
1986 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1988 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1989 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1991 network
["bind_type"] = net_bind_type
1993 if net_provider
!=None:
1994 if net_provider
[:9]=="openflow:":
1996 if net_type
!="ptp" and net_type
!="data":
1997 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2002 if net_type
!="bridge_man" and net_type
!="bridge_data":
2003 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2005 net_type
='bridge_man'
2008 net_type
='bridge_man'
2010 if net_provider
!= None:
2011 if net_provider
[:7]=='bridge:':
2012 #check it is one of the pre-provisioned bridges
2013 bridge_net_name
= net_provider
[7:]
2014 for brnet
in config_dic
['bridge_nets']:
2015 if brnet
[0]==bridge_net_name
: # free
2016 if brnet
[3] != None:
2017 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
2022 # if bridge_net==None:
2023 # 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)
2025 elif config_dic
['network_type'] == 'bridge' and ( net_type
=='bridge_data' or net_type
== 'bridge_man' ):
2026 #look for a free precreated nets
2027 for brnet
in config_dic
['bridge_nets']:
2028 if brnet
[3]==None: # free
2029 if bridge_net
!= None:
2030 if net_type
=='bridge_man': #look for the smaller speed
2031 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
2032 else: #look for the larger speed
2033 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
2037 if bridge_net
==None:
2038 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
2041 print "using net", bridge_net
2042 net_provider
= "bridge:"+bridge_net
[0]
2043 net_vlan
= bridge_net
[1]
2044 elif net_type
== 'bridge_data' or net_type
== 'bridge_man' and config_dic
['network_type'] == 'ovs':
2045 net_provider
= 'OVS'
2046 if not net_vlan
and (net_type
== "data" or net_type
== "ptp" or net_provider
== "OVS"):
2047 net_vlan
= my
.db
.get_free_net_vlan()
2049 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
2051 if net_provider
== 'OVS':
2052 net_provider
= 'OVS' + ":" + str(net_vlan
)
2054 network
['provider'] = net_provider
2055 network
['type'] = net_type
2056 network
['vlan'] = net_vlan
2058 if 'enable_dhcp' in network
and network
['enable_dhcp']:
2059 check_dhcp_data_integrity(network
)
2061 result
, content
= my
.db
.new_row('nets', network
, True, True)
2064 if bridge_net
!=None:
2065 bridge_net
[3] = content
2066 if config_dic
.get("dhcp_server") and config_dic
['network_type'] == 'bridge':
2067 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
2068 config_dic
["dhcp_nets"].append(content
)
2069 print "dhcp_server: add new net", content
2070 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2071 config_dic
["dhcp_nets"].append(content
)
2072 print "dhcp_server: add new net", content
2073 return http_get_network_id(content
)
2075 print "http_post_networks error %d %s" % (result
, content
)
2076 bottle
.abort(-result
, content
)
2080 def check_dhcp_data_integrity(network
):
2082 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
2083 :param network: list with user nets paramters
2088 if "cidr" in network
:
2089 cidr
= network
["cidr"]
2091 ips
= IPNetwork(cidr
)
2092 if "dhcp_first_ip" not in network
:
2093 network
["dhcp_first_ip"] = str(ips
[2])
2094 if "dhcp_last_ip" not in network
:
2095 network
["dhcp_last_ip"] = str(ips
[-2])
2098 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
2099 def http_put_network_id(network_id
):
2100 '''update a network_id into the database.'''
2101 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2103 http_content
= format_in( network_update_schema
)
2104 r
= remove_extra_items(http_content
, network_update_schema
)
2105 change_keys_http2db(http_content
['network'], http2db_network
)
2106 network
=http_content
['network']
2108 #Look for the previous data
2109 where_
= {'uuid': network_id
}
2110 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
2112 print "http_put_network_id error %d %s" % (result
, network_old
)
2113 bottle
.abort(-result
, network_old
)
2116 print "http_put_network_id network '%s' not found" % network_id
2117 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
2120 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
2121 WHERE
={'net_id': network_id
}, LIMIT
=100)
2123 print "http_put_network_id error %d %s" % (result
, network_old
)
2124 bottle
.abort(-result
, content
)
2127 if 'type' in network
and network
['type'] != network_old
[0]['type']:
2128 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
2129 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
2130 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
2133 net_provider
= network
.get('provider', network_old
[0]['provider'])
2134 net_type
= network
.get('type', network_old
[0]['type'])
2135 net_bind_net
= network
.get("bind_net")
2136 net_bind_type
= network
.get("bind_type")
2137 if net_bind_net
!= None:
2138 #look for a valid net
2139 if check_valid_uuid(net_bind_net
):
2140 net_bind_key
= "uuid"
2142 net_bind_key
= "name"
2143 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
2145 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
2148 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
2151 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
2153 network
["bind_net"] = content
[0]["uuid"]
2154 if net_bind_type
!= None:
2155 if net_bind_type
[0:5] != "vlan:":
2156 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
2158 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
2159 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
2161 if net_provider
!=None:
2162 if net_provider
[:9]=="openflow:":
2163 if net_type
!="ptp" and net_type
!="data":
2164 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2166 if net_type
!="bridge_man" and net_type
!="bridge_data":
2167 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2169 #insert in data base
2170 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
2172 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
2173 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
2175 print "http_put_network_id error while launching openflow rules"
2176 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2177 if config_dic
.get("dhcp_server"):
2178 if network_id
in config_dic
["dhcp_nets"]:
2179 config_dic
["dhcp_nets"].remove(network_id
)
2180 print "dhcp_server: delete net", network_id
2181 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
2182 config_dic
["dhcp_nets"].append(network_id
)
2183 print "dhcp_server: add new net", network_id
2185 net_bind
= network
.get("bind", network_old
["bind"] )
2186 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2187 config_dic
["dhcp_nets"].append(network_id
)
2188 print "dhcp_server: add new net", network_id
2189 return http_get_network_id(network_id
)
2191 bottle
.abort(-result
, content
)
2195 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
2196 def http_delete_network_id(network_id
):
2197 '''delete a network_id from the database.'''
2198 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2200 #delete from the data base
2201 result
, content
= my
.db
.delete_row('nets', network_id
)
2204 bottle
.abort(HTTP_Not_Found
, content
)
2206 for brnet
in config_dic
['bridge_nets']:
2207 if brnet
[3]==network_id
:
2210 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
2211 config_dic
["dhcp_nets"].remove(network_id
)
2212 print "dhcp_server: delete net", network_id
2213 data
={'result' : content
}
2214 return format_out(data
)
2216 print "http_delete_network_id error",result
, content
2217 bottle
.abort(-result
, content
)
2222 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
2223 def http_get_openflow_id(network_id
):
2224 '''To obtain the list of openflow rules of a network
2226 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2228 if network_id
=='all':
2231 where_
={"net_id": network_id
}
2232 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2233 WHERE
=where_
, FROM
='of_flows')
2235 bottle
.abort(-result
, content
)
2237 data
={'openflow-rules' : content
}
2238 return format_out(data
)
2240 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
2241 def http_put_openflow_id(network_id
):
2242 '''To make actions over the net. The action is to reinstall the openflow rules
2243 network_id can be 'all'
2245 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2247 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2250 if network_id
=='all':
2253 where_
={"uuid": network_id
}
2254 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
2256 bottle
.abort(-result
, content
)
2260 if net
["type"]!="ptp" and net
["type"]!="data":
2263 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
2265 print "http_put_openflow_id error while launching openflow rules"
2266 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2267 data
={'result' : str(result
)+" nets updates"}
2268 return format_out(data
)
2270 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
2271 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
2272 def http_clear_openflow_rules():
2273 '''To make actions over the net. The action is to delete ALL openflow rules
2275 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2277 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2280 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
2282 print "http_delete_openflow_id error while launching openflow rules"
2283 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2286 data
={'result' : " Clearing openflow rules in process"}
2287 return format_out(data
)
2289 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
2290 def http_get_openflow_ports():
2291 '''Obtain switch ports names of openflow controller
2293 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2294 return format_out(data
)
2301 @bottle.route(url_base
+ '/ports', method
='GET')
2302 def http_get_ports():
2304 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2305 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2306 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2307 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2308 #result, content = my.db.get_ports(where_)
2309 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
2311 print "http_get_ports Error", result
, content
2312 bottle
.abort(-result
, content
)
2315 convert_boolean(content
, ('admin_state_up',) )
2316 delete_nulls(content
)
2317 change_keys_http2db(content
, http2db_port
, reverse
=True)
2318 data
={'ports' : content
}
2319 return format_out(data
)
2321 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2322 def http_get_port_id(port_id
):
2323 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2325 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2327 print "http_get_ports error", result
, content
2328 bottle
.abort(-result
, content
)
2330 print "http_get_ports port '%s' not found" % str(port_id
)
2331 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2333 convert_boolean(content
, ('admin_state_up',) )
2334 delete_nulls(content
)
2335 change_keys_http2db(content
, http2db_port
, reverse
=True)
2336 data
={'port' : content
[0]}
2337 return format_out(data
)
2340 @bottle.route(url_base
+ '/ports', method
='POST')
2341 def http_post_ports():
2342 '''insert an external port into the database.'''
2343 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2345 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2347 http_content
= format_in( port_new_schema
)
2348 r
= remove_extra_items(http_content
, port_new_schema
)
2349 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2350 change_keys_http2db(http_content
['port'], http2db_port
)
2351 port
=http_content
['port']
2353 port
['type'] = 'external'
2354 if 'net_id' in port
and port
['net_id'] == None:
2357 if 'net_id' in port
:
2358 #check that new net has the correct type
2359 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2361 bottle
.abort(HTTP_Bad_Request
, new_net
)
2363 #insert in data base
2364 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2366 if 'net_id' in port
:
2367 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2369 print "http_post_ports error while launching openflow rules"
2370 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2371 return http_get_port_id(uuid
)
2373 bottle
.abort(-result
, uuid
)
2376 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2377 def http_put_port_id(port_id
):
2378 '''update a port_id into the database.'''
2380 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2382 http_content
= format_in( port_update_schema
)
2383 change_keys_http2db(http_content
['port'], http2db_port
)
2384 port_dict
=http_content
['port']
2386 #Look for the previous port data
2387 where_
= {'uuid': port_id
}
2388 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2390 print "http_put_port_id error", result
, content
2391 bottle
.abort(-result
, content
)
2394 print "http_put_port_id port '%s' not found" % port_id
2395 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2398 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2399 if k
in port_dict
and not my
.admin
:
2400 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2404 #change_keys_http2db(port, http2db_port, reverse=True)
2408 if 'net_id' in port_dict
:
2410 old_net
= port
.get('net_id', None)
2411 new_net
= port_dict
['net_id']
2412 if old_net
!= new_net
:
2414 if new_net
is not None: nets
.append(new_net
) #put first the new net, so that new openflow rules are created before removing the old ones
2415 if old_net
is not None: nets
.append(old_net
)
2416 if port
['type'] == 'instance:bridge' or port
['type'] == 'instance:ovs':
2417 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2419 elif port
['type'] == 'external':
2421 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2425 #check that new net has the correct type
2426 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2428 #change VLAN for SR-IOV ports
2429 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2431 port_dict
["vlan"] = None
2433 port_dict
["vlan"] = new_net_dict
["vlan"]
2434 #get host where this VM is allocated
2435 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2437 print "http_put_port_id database error", content
2439 host_id
= content
[0]["host_id"]
2441 #insert in data base
2443 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2445 #Insert task to complete actions
2448 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2449 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2450 #TODO Do something if fails
2452 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2455 return http_get_port_id(port_id
)
2457 bottle
.abort(HTTP_Bad_Request
, content
)
2461 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2462 def http_delete_port_id(port_id
):
2463 '''delete a port_id from the database.'''
2464 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2466 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2469 #Look for the previous port data
2470 where_
= {'uuid': port_id
, "type": "external"}
2471 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2474 print "http_delete_port_id port '%s' not found" % port_id
2475 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2477 #delete from the data base
2478 result
, content
= my
.db
.delete_row('ports', port_id
)
2481 bottle
.abort(HTTP_Not_Found
, content
)
2483 network
= ports
[0].get('net_id', None)
2484 if network
is not None:
2486 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2487 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2488 data
={'result' : content
}
2489 return format_out(data
)
2491 print "http_delete_port_id error",result
, content
2492 bottle
.abort(-result
, content
)