You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
434 lines
20 KiB
434 lines
20 KiB
# Copyright (c) 2015 OpenStack Foundation. |
|
# All Rights Reserved. |
|
# |
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
|
# not use this file except in compliance with the License. You may obtain |
|
# a copy of the License at |
|
# |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
|
# |
|
# Unless required by applicable law or agreed to in writing, software |
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
# License for the specific language governing permissions and limitations |
|
# under the License. |
|
|
|
import random |
|
import socket |
|
|
|
from oslo_log import log as logging |
|
from oslo_serialization import jsonutils |
|
from oslo_utils import excutils |
|
|
|
from networking_l2gw.services.l2gateway.agent.ovsdb import base_connection |
|
from networking_l2gw.services.l2gateway.common import constants as n_const |
|
from networking_l2gw.services.l2gateway.common import ovsdb_schema |
|
from networking_l2gw.services.l2gateway import exceptions |
|
|
|
LOG = logging.getLogger(__name__) |
|
|
|
|
|
class OVSDBWriter(base_connection.BaseConnection): |
|
"""Performs transactions to OVSDB server tables.""" |
|
def __init__(self, conf, gw_config, mgr=None): |
|
super(OVSDBWriter, self).__init__(conf, gw_config, mgr=None) |
|
self.mgr = mgr |
|
|
|
def _process_response(self, op_id): |
|
result = self._response(op_id) |
|
error = result.get("error", None) |
|
if error: |
|
raise exceptions.OVSDBError( |
|
message="Error from the OVSDB server: %s" % error |
|
) |
|
# Check errors in responses of all the subqueries |
|
outcomes = result.get("result", None) |
|
if outcomes: |
|
for outcome in outcomes: |
|
error = outcome.get("error", None) |
|
if error: |
|
raise exceptions.OVSDBError( |
|
message="Error from the OVSDB server: %s" % error) |
|
return result |
|
|
|
def _get_reply(self, operation_id, ovsdb_identifier): |
|
count = 0 |
|
while count <= n_const.MAX_RETRIES: |
|
response = self._recv_data(ovsdb_identifier) |
|
LOG.debug("Response from OVSDB server = %s", str(response)) |
|
if response: |
|
try: |
|
json_m = jsonutils.loads(response) |
|
self.responses.append(json_m) |
|
method_type = json_m.get('method', None) |
|
if method_type == "echo" and self.enable_manager: |
|
self.ovsdb_dicts.get(ovsdb_identifier).send( |
|
jsonutils.dumps( |
|
{"result": json_m.get("params", None), |
|
"error": None, "id": json_m['id']})) |
|
else: |
|
if self._process_response(operation_id): |
|
return True |
|
except Exception as ex: |
|
with excutils.save_and_reraise_exception(): |
|
LOG.exception("Exception while receiving the " |
|
"response for the write request:" |
|
" [%s]", ex) |
|
count += 1 |
|
with excutils.save_and_reraise_exception(): |
|
LOG.error("Could not obtain response from the OVSDB server " |
|
"for the request") |
|
|
|
def _send_and_receive(self, query, operation_id, ovsdb_identifier, |
|
rcv_required): |
|
if not self.send(query, addr=ovsdb_identifier): |
|
return |
|
if rcv_required: |
|
self._get_reply(operation_id, ovsdb_identifier) |
|
|
|
def delete_logical_switch(self, logical_switch_uuid, ovsdb_identifier, |
|
rcv_required=True): |
|
"""Delete an entry from Logical_Switch OVSDB table.""" |
|
commit_dict = {"op": "commit", "durable": True} |
|
op_id = str(random.getrandbits(128)) |
|
query = {"method": "transact", |
|
"params": [n_const.OVSDB_SCHEMA_NAME, |
|
{"op": "delete", |
|
"table": "Logical_Switch", |
|
"where": [["_uuid", "==", |
|
["uuid", logical_switch_uuid]]]}, |
|
commit_dict], |
|
"id": op_id} |
|
LOG.debug("delete_logical_switch: query: %s", query) |
|
self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) |
|
|
|
def insert_ucast_macs_remote(self, l_switch_dict, locator_dict, |
|
mac_dict, ovsdb_identifier, |
|
rcv_required=True): |
|
"""Insert an entry in Ucast_Macs_Remote OVSDB table.""" |
|
# To insert an entry in Ucast_Macs_Remote table, it requires |
|
# corresponding entry in Physical_Locator (Compute node VTEP IP) |
|
# and Logical_Switch (Neutron network) tables. |
|
logical_switch = ovsdb_schema.LogicalSwitch(l_switch_dict['uuid'], |
|
l_switch_dict['name'], |
|
l_switch_dict['key'], |
|
l_switch_dict['description' |
|
]) |
|
locator = ovsdb_schema.PhysicalLocator(locator_dict['uuid'], |
|
locator_dict['dst_ip']) |
|
macObject = ovsdb_schema.UcastMacsRemote(mac_dict['uuid'], |
|
mac_dict['mac'], |
|
mac_dict['logical_switch_id'], |
|
mac_dict['physical_locator_id' |
|
], |
|
mac_dict['ip_address']) |
|
# Form the insert query now. |
|
commit_dict = {"op": "commit", "durable": True} |
|
op_id = str(random.getrandbits(128)) |
|
params = [n_const.OVSDB_SCHEMA_NAME] |
|
|
|
if locator.uuid: |
|
locator_list = ['uuid', locator.uuid] |
|
else: |
|
locator.uuid = ''.join(['a', str(random.getrandbits(128))]) |
|
locator_list = ["named-uuid", locator.uuid] |
|
params.append(self._get_physical_locator_dict(locator)) |
|
|
|
if logical_switch.uuid: |
|
l_switches = ['uuid', logical_switch.uuid] |
|
else: |
|
logical_switch.uuid = ''.join(['a', str(random.getrandbits(128))]) |
|
l_switches = ["named-uuid", logical_switch.uuid] |
|
params.append(self._get_logical_switch_dict(logical_switch)) |
|
|
|
params.append(self._get_ucast_macs_remote_dict( |
|
macObject, locator_list, l_switches)) |
|
params.append(commit_dict) |
|
query = {"method": "transact", |
|
"params": params, |
|
"id": op_id} |
|
LOG.debug("insert_ucast_macs_remote: query: %s", query) |
|
self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) |
|
|
|
def update_ucast_macs_remote(self, locator_dict, mac_dict, |
|
ovsdb_identifier, |
|
rcv_required=True): |
|
"""Update an entry in Ucast_Macs_Remote OVSDB table.""" |
|
# It is possible that the locator may not exist already. |
|
locator = ovsdb_schema.PhysicalLocator(locator_dict['uuid'], |
|
locator_dict['dst_ip']) |
|
macObject = ovsdb_schema.UcastMacsRemote(mac_dict['uuid'], |
|
mac_dict['mac'], |
|
mac_dict['logical_switch_id'], |
|
mac_dict['physical_locator_id' |
|
], |
|
mac_dict['ip_address']) |
|
# Form the insert query now. |
|
commit_dict = {"op": "commit", "durable": True} |
|
op_id = str(random.getrandbits(128)) |
|
params = [n_const.OVSDB_SCHEMA_NAME] |
|
|
|
# If the physical_locator does not exist (VM moving to a new compute |
|
# node), then insert a new record in Physical_Locator first. |
|
if locator.uuid: |
|
locator_list = ['uuid', locator.uuid] |
|
else: |
|
locator.uuid = ''.join(['a', str(random.getrandbits(128))]) |
|
locator_list = ["named-uuid", locator.uuid] |
|
params.append(self._get_physical_locator_dict(locator)) |
|
|
|
params.append(self._get_dict_for_update_ucast_mac_remote( |
|
macObject, locator_list)) |
|
params.append(commit_dict) |
|
query = {"method": "transact", |
|
"params": params, |
|
"id": op_id} |
|
LOG.debug("update_ucast_macs_remote: query: %s", query) |
|
self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) |
|
|
|
def delete_ucast_macs_remote(self, logical_switch_uuid, macs, |
|
ovsdb_identifier, |
|
rcv_required=True): |
|
"""Delete entries from Ucast_Macs_Remote OVSDB table.""" |
|
commit_dict = {"op": "commit", "durable": True} |
|
op_id = str(random.getrandbits(128)) |
|
params = [n_const.OVSDB_SCHEMA_NAME] |
|
for mac in macs: |
|
sub_query = {"op": "delete", |
|
"table": "Ucast_Macs_Remote", |
|
"where": [["MAC", |
|
"==", |
|
mac], |
|
["logical_switch", |
|
"==", |
|
["uuid", |
|
logical_switch_uuid]]]} |
|
params.append(sub_query) |
|
params.append(commit_dict) |
|
query = {"method": "transact", |
|
"params": params, |
|
"id": op_id} |
|
LOG.debug("delete_ucast_macs_remote: query: %s", query) |
|
self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) |
|
|
|
def update_connection_to_gateway(self, logical_switch_dict, |
|
locator_dicts, mac_dicts, |
|
port_dicts, ovsdb_identifier, |
|
op_method, |
|
rcv_required=True): |
|
"""Updates Physical Port's VNI to VLAN binding.""" |
|
# Form the JSON Query so as to update the physical port with the |
|
# vni-vlan (logical switch uuid to vlan) binding |
|
update_dicts = self._get_bindings_to_update(logical_switch_dict, |
|
locator_dicts, |
|
mac_dicts, |
|
port_dicts, |
|
op_method) |
|
op_id = str(random.getrandbits(128)) |
|
query = {"method": "transact", |
|
"params": update_dicts, |
|
"id": op_id} |
|
LOG.debug("update_connection_to_gateway: query = %s", query) |
|
self._send_and_receive(query, op_id, ovsdb_identifier, rcv_required) |
|
|
|
def _recv_data(self, ovsdb_identifier): |
|
chunks = [] |
|
lc = rc = 0 |
|
prev_char = None |
|
while True: |
|
try: |
|
if self.enable_manager: |
|
response = self.ovsdb_dicts.get(ovsdb_identifier).recv( |
|
n_const.BUFFER_SIZE) |
|
else: |
|
response = self.socket.recv(n_const.BUFFER_SIZE) |
|
if response: |
|
response = response.decode('utf8') |
|
for i, c in enumerate(response): |
|
if c == '{' and not (prev_char and |
|
prev_char == '\\'): |
|
lc += 1 |
|
elif c == '}' and not (prev_char and |
|
prev_char == '\\'): |
|
rc += 1 |
|
if lc == rc and lc != 0: |
|
chunks.append(response[0:i + 1]) |
|
message = "".join(chunks) |
|
return message |
|
prev_char = c |
|
chunks.append(response) |
|
else: |
|
LOG.warning("Did not receive any reply from the OVSDB " |
|
"server") |
|
return |
|
except (socket.error, socket.timeout): |
|
LOG.warning("Did not receive any reply from the OVSDB " |
|
"server") |
|
return |
|
|
|
def _get_bindings_to_update(self, l_switch_dict, locator_dicts, |
|
mac_dicts, port_dicts, op_method): |
|
# For connection-create, there are two cases to be handled |
|
# Case 1: VMs exist in a network on compute nodes. |
|
# Connection request will contain locators, ports, MACs and |
|
# network. |
|
# Case 2: VMs do not exist in a network on compute nodes. |
|
# Connection request will contain only ports and network |
|
# |
|
# For connection-delete, we do not need logical_switch and locators |
|
# information, we just need ports. |
|
locator_list = [] |
|
port_list = [] |
|
ls_list = [] |
|
logical_switch = None |
|
# Convert logical switch dict to a class object |
|
if l_switch_dict: |
|
logical_switch = ovsdb_schema.LogicalSwitch( |
|
l_switch_dict['uuid'], |
|
l_switch_dict['name'], |
|
l_switch_dict['key'], |
|
l_switch_dict['description']) |
|
|
|
# Convert locator dicts into class objects |
|
for locator in locator_dicts: |
|
locator_list.append(ovsdb_schema.PhysicalLocator(locator['uuid'], |
|
locator['dst_ip']) |
|
) |
|
|
|
# Convert MAC dicts into class objects. mac_dicts is a dictionary with |
|
# locator VTEP IP as the key and list of MACs as the value. |
|
locator_macs = {} |
|
for locator_ip, mac_list in mac_dicts.items(): |
|
mac_object_list = [] |
|
for mac_dict in mac_list: |
|
mac_object = ovsdb_schema.UcastMacsRemote( |
|
mac_dict['uuid'], |
|
mac_dict['mac'], |
|
mac_dict['logical_switch_id'], |
|
mac_dict['physical_locator_id'], |
|
mac_dict['ip_address']) |
|
mac_object_list.append(mac_object) |
|
locator_macs[locator_ip] = mac_object_list |
|
|
|
# Convert port dicts into class objects |
|
for port in port_dicts: |
|
phys_port = ovsdb_schema.PhysicalPort(port['uuid'], |
|
port['name'], |
|
port['physical_switch_id'], |
|
port['vlan_bindings'], |
|
port['port_fault_status']) |
|
port_list.append(phys_port) |
|
|
|
bindings = [] |
|
bindings.append(n_const.OVSDB_SCHEMA_NAME) |
|
|
|
# Form the query. |
|
commit_dict = {"op": "commit", "durable": True} |
|
params = [n_const.OVSDB_SCHEMA_NAME] |
|
|
|
# Use logical switch |
|
if logical_switch: |
|
ls_list = self._form_logical_switch(logical_switch, params) |
|
|
|
# Use physical locators |
|
if locator_list: |
|
self._form_physical_locators(ls_list, locator_list, locator_macs, |
|
params) |
|
|
|
# Use ports |
|
self._form_ports(ls_list, port_list, params, op_method) |
|
params.append(commit_dict) |
|
return params |
|
|
|
def _form_logical_switch(self, logical_switch, params): |
|
ls_list = [] |
|
if logical_switch.uuid: |
|
ls_list = ['uuid', logical_switch.uuid] |
|
else: |
|
logical_switch.uuid = ''.join(['a', str(random.getrandbits(128))]) |
|
ls_list = ["named-uuid", logical_switch.uuid] |
|
params.append(self._get_logical_switch_dict(logical_switch)) |
|
return ls_list |
|
|
|
def _form_physical_locators(self, ls_list, locator_list, locator_macs, |
|
params): |
|
for locator in locator_list: |
|
if locator.uuid: |
|
loc_list = ['uuid', locator.uuid] |
|
else: |
|
locator.uuid = ''.join(['a', str(random.getrandbits(128))]) |
|
loc_list = ["named-uuid", locator.uuid] |
|
params.append(self._get_physical_locator_dict(locator)) |
|
macs = locator_macs.get(locator.dst_ip, None) |
|
if macs: |
|
for mac in macs: |
|
query = self._get_ucast_macs_remote_dict(mac, |
|
loc_list, |
|
ls_list) |
|
params.append(query) |
|
|
|
def _form_ports(self, ls_list, port_list, params, op_method): |
|
for port in port_list: |
|
port_vlan_bindings = [] |
|
outer_list = [] |
|
port_vlan_bindings.append("map") |
|
if port.vlan_bindings: |
|
for vlan_binding in port.vlan_bindings: |
|
if vlan_binding.logical_switch_uuid: |
|
outer_list.append([vlan_binding.vlan, |
|
['uuid', |
|
vlan_binding.logical_switch_uuid]]) |
|
else: |
|
outer_list.append([vlan_binding.vlan, |
|
ls_list]) |
|
port_vlan_bindings.append(outer_list) |
|
if op_method == 'CREATE': |
|
update_dict = {"op": "mutate", |
|
"table": "Physical_Port", |
|
"where": [["_uuid", "==", |
|
["uuid", port.uuid]]], |
|
"mutations": [["vlan_bindings", |
|
"insert", |
|
port_vlan_bindings]]} |
|
elif op_method == 'DELETE': |
|
update_dict = {"op": "mutate", |
|
"table": "Physical_Port", |
|
"where": [["_uuid", "==", |
|
["uuid", port.uuid]]], |
|
"mutations": [["vlan_bindings", |
|
"delete", |
|
port_vlan_bindings]]} |
|
params.append(update_dict) |
|
|
|
def _get_physical_locator_dict(self, locator): |
|
return {"op": "insert", |
|
"table": "Physical_Locator", |
|
"uuid-name": locator.uuid, |
|
"row": {"dst_ip": locator.dst_ip, |
|
"encapsulation_type": "vxlan_over_ipv4"}} |
|
|
|
def _get_logical_switch_dict(self, logical_switch): |
|
return {"op": "insert", |
|
"uuid-name": logical_switch.uuid, |
|
"table": "Logical_Switch", |
|
"row": {"description": logical_switch.description, |
|
"name": logical_switch.name, |
|
"tunnel_key": int(logical_switch.key)}} |
|
|
|
def _get_ucast_macs_remote_dict(self, mac, locator_list, |
|
logical_switch_list): |
|
named_string = str(random.getrandbits(128)) |
|
return {"op": "insert", |
|
"uuid-name": ''.join(['a', named_string]), |
|
"table": "Ucast_Macs_Remote", |
|
"row": {"MAC": mac.mac, |
|
"ipaddr": mac.ip_address, |
|
"locator": locator_list, |
|
"logical_switch": logical_switch_list}} |
|
|
|
def _get_dict_for_update_ucast_mac_remote(self, mac, locator_list): |
|
return {"op": "update", |
|
"table": "Ucast_Macs_Remote", |
|
"where": [["_uuid", "==", |
|
["uuid", mac.uuid]]], |
|
"row": {"locator": locator_list}}
|
|
|