X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=ovim.py;h=d15fdbda721e1511fcb34ab3e61bfa8f2a3cc6df;hb=refs%2Fchanges%2F21%2F1121%2F2;hp=e5cd40d0169bf68898cf2e43342ab56012256348;hpb=57f7bda62b86d19fbcd7b90271c7150ca8a8ab18;p=osm%2Fopenvim.git diff --git a/ovim.py b/ovim.py index e5cd40d..d15fdbd 100644 --- a/ovim.py +++ b/ovim.py @@ -37,6 +37,8 @@ import imp import host_thread as ht import dhcp_thread as dt import openflow_thread as oft +from netaddr import IPNetwork +from jsonschema import validate as js_v, exceptions as js_e HTTP_Bad_Request = 400 HTTP_Unauthorized = 401 @@ -102,6 +104,40 @@ class ovim(): self.config['db_host']) ) return db + @staticmethod + def _check_dhcp_data_integrity(network): + """ + Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value + :param network: list with user nets paramters + :return: + """ + if "cidr" in network: + cidr = network["cidr"] + ip_tools = IPNetwork(cidr) + cidr_len = ip_tools.prefixlen + if cidr_len > 29: + return False + + ips = IPNetwork(cidr) + if "dhcp_first_ip" not in network: + network["dhcp_first_ip"] = str(ips[2]) + if "dhcp_last_ip" not in network: + network["dhcp_last_ip"] = str(ips[-2]) + if "gateway_ip" not in network: + network["gateway_ip"] = str(ips[1]) + + return True + else: + return False + + @staticmethod + def _check_valid_uuid(uuid): + id_schema = {"type": "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"} + try: + js_v(uuid, id_schema) + return True + except js_e.ValidationError: + return False def start_service(self): #if self.running_info: @@ -228,6 +264,22 @@ class ovim(): thread.start() self.config['host_threads'][host['uuid']] = thread + # create ovs dhcp thread + result, content = self.db.get_table(FROM='nets') + if result < 0: + self.logger.error("http_get_ports Error %d %s", result, content) + raise ovimException(str(content), -result) + + for net in content: + net_type = net['type'] + if (net_type == 'bridge_data' or net_type == 'bridge_man') \ + and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true": + self.launch_dhcp_server(net['vlan'], + net['dhcp_first_ip'], + net['dhcp_last_ip'], + net['cidr'], + net['gateway_ip']) + def stop_service(self): threads = self.config.get('host_threads', {}) if 'of_thread' in self.config: @@ -239,10 +291,366 @@ class ovim(): thread.insert_task("exit") for thread in threads.values(): thread.join() - # http_thread.join() - # if http_thread_admin is not None: - # http_thread_admin.join() + def get_networks(self, columns=None, filter={}, limit=None): + """ + Retreive networks available + :param columns: List with select query parameters + :param filter: List with where query parameters + :param limit: Query limit result + :return: + """ + result, content = self.db.get_table(SELECT=columns, FROM='nets', WHERE=filter, LIMIT=limit) + + if result < 0: + raise ovimException(str(content), -result) + + convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp')) + + return content + + def show_network(self, network_id, filter={}): + """ + Get network from DB by id + :param network_id: net Id + :param filter: + :param limit: + :return: + """ + # obtain data + if not network_id: + raise ovimException("Not network id was not found") + filter['uuid'] = network_id + + result, content = self.db.get_table(FROM='nets', WHERE=filter, LIMIT=100) + + if result < 0: + raise ovimException(str(content), -result) + elif result == 0: + raise ovimException("show_network network '%s' not found" % network_id, -result) + else: + convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp')) + # get ports from DB + result, ports = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',), + WHERE={'net_id': network_id}, LIMIT=100) + if len(ports) > 0: + content[0]['ports'] = ports + + convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp')) + return content[0] + + def new_network(self, network): + """ + Create a net in DB + :return: + """ + tenant_id = network.get('tenant_id') + + if tenant_id: + result, _ = self.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id, "enabled": True}) + if result <= 0: + raise ovimException("set_network error, no tenant founded", -result) + + bridge_net = None + # check valid params + net_provider = network.get('provider') + net_type = network.get('type') + net_vlan = network.get("vlan") + net_bind_net = network.get("bind_net") + net_bind_type = network.get("bind_type") + name = network["name"] + + # check if network name ends with : and network exist in order to make and automated bindning + vlan_index = name.rfind(":") + if not net_bind_net and not net_bind_type and vlan_index > 1: + try: + vlan_tag = int(name[vlan_index + 1:]) + if not vlan_tag and vlan_tag < 4096: + net_bind_net = name[:vlan_index] + net_bind_type = "vlan:" + name[vlan_index + 1:] + except: + pass + + if net_bind_net: + # look for a valid net + if self._check_valid_uuid(net_bind_net): + net_bind_key = "uuid" + else: + net_bind_key = "name" + result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net}) + if result < 0: + raise ovimException(' getting nets from db ' + content, HTTP_Internal_Server_Error) + elif result == 0: + raise ovimException(" bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request) + elif result > 1: + raise ovimException(" more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), HTTP_Bad_Request) + network["bind_net"] = content[0]["uuid"] + + if net_bind_type: + if net_bind_type[0:5] != "vlan:": + raise ovimException("bad format for 'bind_type', must be 'vlan:'", HTTP_Bad_Request) + if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0: + raise ovimException("bad format for 'bind_type', must be 'vlan:' with a tag between 1 and 4095", + HTTP_Bad_Request) + network["bind_type"] = net_bind_type + + if net_provider: + if net_provider[:9] == "openflow:": + if net_type: + if net_type != "ptp" and net_type != "data": + raise ovimException(" only 'ptp' or 'data' net types can be bound to 'openflow'", + HTTP_Bad_Request) + else: + net_type = 'data' + else: + if net_type: + if net_type != "bridge_man" and net_type != "bridge_data": + raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound " + "to 'bridge', 'macvtap' or 'default", HTTP_Bad_Request) + else: + net_type = 'bridge_man' + + if not net_type: + net_type = 'bridge_man' + + if net_provider: + if net_provider[:7] == 'bridge:': + # check it is one of the pre-provisioned bridges + bridge_net_name = net_provider[7:] + for brnet in self.config['bridge_nets']: + if brnet[0] == bridge_net_name: # free + if not brnet[3]: + raise ovimException("invalid 'provider:physical', " + "bridge '%s' is already used" % bridge_net_name, HTTP_Conflict) + bridge_net = brnet + net_vlan = brnet[1] + break + # if bridge_net==None: + # 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) + # return + + elif self.config['network_type'] == 'bridge' and (net_type == 'bridge_data' or net_type == 'bridge_man'): + # look for a free precreated nets + for brnet in self.config['bridge_nets']: + if not brnet[3]: # free + if not bridge_net: + if net_type == 'bridge_man': # look for the smaller speed + if brnet[2] < bridge_net[2]: + bridge_net = brnet + else: # look for the larger speed + if brnet[2] > bridge_net[2]: + bridge_net = brnet + else: + bridge_net = brnet + net_vlan = brnet[1] + if not bridge_net: + raise ovimException("Max limits of bridge networks reached. Future versions of VIM " + "will overcome this limit", HTTP_Bad_Request) + else: + self.logger.debug("using net " + bridge_net) + net_provider = "bridge:" + bridge_net[0] + net_vlan = bridge_net[1] + elif net_type == 'bridge_data' or net_type == 'bridge_man' and self.config['network_type'] == 'ovs': + net_provider = 'OVS' + if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"): + net_vlan = self.db.get_free_net_vlan() + if net_vlan < 0: + raise ovimException("Error getting an available vlan", HTTP_Internal_Server_Error) + if net_provider == 'OVS': + net_provider = 'OVS' + ":" + str(net_vlan) + + network['provider'] = net_provider + network['type'] = net_type + network['vlan'] = net_vlan + dhcp_integrity = True + if 'enable_dhcp' in network and network['enable_dhcp']: + dhcp_integrity = self._check_dhcp_data_integrity(network) + + result, content = self.db.new_row('nets', network, True, True) + + if result >= 0 and dhcp_integrity: + if bridge_net: + bridge_net[3] = content + if self.config.get("dhcp_server") and self.config['network_type'] == 'bridge': + if network["name"] in self.config["dhcp_server"].get("nets", ()): + self.config["dhcp_nets"].append(content) + self.logger.debug("dhcp_server: add new net", content) + elif not bridge_net and bridge_net[0] in self.config["dhcp_server"].get("bridge_ifaces", ()): + self.config["dhcp_nets"].append(content) + self.logger.debug("dhcp_server: add new net", content, content) + return content + else: + raise ovimException("Error posting network", HTTP_Internal_Server_Error) +# TODO kei change update->edit + + def edit_network(self, network_id, network): + """ + Update entwork data byt id + :return: + """ + # Look for the previous data + where_ = {'uuid': network_id} + result, network_old = self.db.get_table(FROM='nets', WHERE=where_) + if result < 0: + raise ovimException("Error updating network %s" % network_old, HTTP_Internal_Server_Error) + elif result == 0: + raise ovimException('network %s not found' % network_id, HTTP_Not_Found) + # get ports + nbports, content = self.db.get_table(FROM='ports', SELECT=('uuid as port_id',), + WHERE={'net_id': network_id}, LIMIT=100) + if result < 0: + raise ovimException("http_put_network_id error %d %s" % (result, network_old), HTTP_Internal_Server_Error) + if nbports > 0: + if 'type' in network and network['type'] != network_old[0]['type']: + raise ovimException("Can not change type of network while having ports attached", + HTTP_Method_Not_Allowed) + if 'vlan' in network and network['vlan'] != network_old[0]['vlan']: + raise ovimException("Can not change vlan of network while having ports attached", + HTTP_Method_Not_Allowed) + + # check valid params + net_provider = network.get('provider', network_old[0]['provider']) + net_type = network.get('type', network_old[0]['type']) + net_bind_net = network.get("bind_net") + net_bind_type = network.get("bind_type") + if net_bind_net: + # look for a valid net + if self._check_valid_uuid(net_bind_net): + net_bind_key = "uuid" + else: + net_bind_key = "name" + result, content = self.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net}) + if result < 0: + raise ovimException('Getting nets from db ' + content, HTTP_Internal_Server_Error) + elif result == 0: + raise ovimException("bind_net %s '%s'not found" % (net_bind_key, net_bind_net), HTTP_Bad_Request) + elif result > 1: + raise ovimException("More than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net), + HTTP_Bad_Request) + network["bind_net"] = content[0]["uuid"] + if net_bind_type: + if net_bind_type[0:5] != "vlan:": + raise ovimException("Bad format for 'bind_type', must be 'vlan:'", HTTP_Bad_Request) + if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:]) <= 0: + raise ovimException("bad format for 'bind_type', must be 'vlan:' with a tag between 1 and 4095", + HTTP_Bad_Request) + if net_provider: + if net_provider[:9] == "openflow:": + if net_type != "ptp" and net_type != "data": + raise ovimException("Only 'ptp' or 'data' net types can be bound to 'openflow'", HTTP_Bad_Request) + else: + if net_type != "bridge_man" and net_type != "bridge_data": + raise ovimException("Only 'bridge_man' or 'bridge_data' net types can be bound to " + "'bridge', 'macvtap' or 'default", HTTP_Bad_Request) + + # insert in data base + result, content = self.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True) + if result >= 0: + # if result > 0 and nbports>0 and 'admin_state_up' in network + # and network['admin_state_up'] != network_old[0]['admin_state_up']: + if result > 0: + r, c = self.config['of_thread'].insert_task("update-net", network_id) + if r < 0: + raise ovimException("Error while launching openflow rules %s" % c, HTTP_Internal_Server_Error) + if self.config.get("dhcp_server"): + if network_id in self.config["dhcp_nets"]: + self.config["dhcp_nets"].remove(network_id) + if network.get("name", network_old["name"]) in self.config["dhcp_server"].get("nets", ()): + self.config["dhcp_nets"].append(network_id) + else: + net_bind = network.get("bind", network_old["bind"]) + if net_bind and net_bind[:7] == "bridge:" and net_bind[7:] in self.config["dhcp_server"].get( + "bridge_ifaces", ()): + self.config["dhcp_nets"].append(network_id) + return network_id + else: + raise ovimException(content, -result) + + def delete_network(self, network_id): + + # delete from the data base + result, content = self.db.delete_row('nets', network_id) + + if result == 0: + raise ovimException("Network %s not found " % network_id, HTTP_Not_Found) + elif result > 0: + for brnet in self.config['bridge_nets']: + if brnet[3] == network_id: + brnet[3] = None + break + if self.config.get("dhcp_server") and network_id in self.config["dhcp_nets"]: + self.config["dhcp_nets"].remove(network_id) + return content + else: + raise ovimException("Error deleting network %s" % network_id, HTTP_Internal_Server_Error) + + def get_openflow_rules(self, network_id=None): + """ + Get openflow id from DB + :param network_id: Network id, if none all networks will be retrieved + :return: Return a list with Openflow rules per net + """ + # ignore input data + if not network_id: + where_ = {} + else: + where_ = {"net_id": network_id} + + result, content = self.db.get_table( + SELECT=("name", "net_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"), + WHERE=where_, FROM='of_flows') + + if result < 0: + raise ovimException(str(content), -result) + return content + + def edit_openflow_rules(self, network_id=None): + + """ + To make actions over the net. The action is to reinstall the openflow rules + network_id can be 'all' + :param network_id: Network id, if none all networks will be retrieved + :return : Number of nets updated + """ + + # ignore input data + if not network_id: + where_ = {} + else: + where_ = {"uuid": network_id} + result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets') + + if result < 0: + raise ovimException(str(content), -result) + + for net in content: + if net["type"] != "ptp" and net["type"] != "data": + result -= 1 + continue + r, c = self.config['of_thread'].insert_task("update-net", net['uuid']) + if r < 0: + raise ovimException(str(c), -r) + return result + + def delete_openflow_rules(self): + """ + To make actions over the net. The action is to delete ALL openflow rules + :return: return operation result + """ + # ignore input data + r, c = self.config['of_thread'].insert_task("clear-all") + if r < 0: + raise ovimException(str(c), -r) + return r + + def get_openflow_ports(self): + """ + Obtain switch ports names of openflow controller + :return: Return flow ports in DB + """ + data = {'ports': self.config['of_thread'].OF_connector.pp2ofi} + return data def get_ports(self, columns=None, filter={}, limit=None): # result, content = my.db.get_ports(where_) @@ -254,7 +662,6 @@ class ovim(): convert_boolean(content, ('admin_state_up',)) return content - def new_port(self, port_data): port_data['type'] = 'external' if port_data.get('net_id'): @@ -294,7 +701,6 @@ class ovim(): self.logger.error("Cannot insert a task for updating network '$s' %s", network, c) return content - def edit_port(self, port_id, port_data, admin=True): # Look for the previous port data result, content = self.db.get_table(FROM="ports", WHERE={'uuid': port_id}) @@ -356,3 +762,54 @@ class ovim(): return port_id else: raise ovimException("Error {}".format(content), http_code=-result) + + def get_dhcp_controller(self): + """ + Create an host_thread object for manage openvim controller and not create a thread for itself + :return: dhcp_host openvim controller object + """ + + if 'openvim_controller' in self.config['host_threads']: + return self.config['host_threads']['openvim_controller'] + + bridge_ifaces = [] + controller_ip = self.config['ovs_controller_ip'] + ovs_controller_user = self.config['ovs_controller_user'] + + host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False + host_develop_mode = True if self.config['mode'] == 'development' else False + + dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip, + db=self.config['db'], + db_lock=self.config['db_lock'], test=host_test_mode, + image_path=self.config['image_path'], version=self.config['version'], + host_id='openvim_controller', develop_mode=host_develop_mode, + develop_bridge_iface=bridge_ifaces) + + self.config['host_threads']['openvim_controller'] = dhcp_host + if not host_test_mode: + dhcp_host.ssh_connect() + return dhcp_host + + def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway): + """ + Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes + :param vlan: vlan identifier + :param first_ip: First dhcp range ip + :param last_ip: Last dhcp range ip + :param cidr: net cidr + :param gateway: net gateway + :return: + """ + ip_tools = IPNetwork(cidr) + dhcp_netmask = str(ip_tools.netmask) + ip_range = [first_ip, last_ip] + + dhcp_path = self.config['ovs_controller_file_path'] + + controller_host = self.get_dhcp_controller() + controller_host.create_linux_bridge(vlan) + controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask) + controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway) + +