Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ services:
- MQ_PASS=${MQ_PASS:-guest}
# OXP settings.
- OXP_PROVISION_URL=${OXP_PROVISION_URL}
- OXP_PULL_URL=${OXP_PULL_URL}
- OXP_PULL_INTERVAL=${OXP_PULL_INTERVAL}
- OXP_TOPOLOGY_URL=${OXP_TOPOLOGY_URL}
- OXP_PULL_TOPOLOGY_INTERVAL=${OXP_PULL_TOPOLOGY_INTERVAL}
- OXP_CONNECTION_URL=${OXP_CONNECTION_URL}

volumes:
Expand Down
6 changes: 4 additions & 2 deletions env.template
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ MQ_PASS=guest

# Kytos/OESS API address
OXP_PROVISION_URL=http://192.168.201.205:8088/SDX-LC/1.0.0/provision
OXP_PULL_URL=http://192.168.201.205:8088/SDX-LC/1.0.0/topology
OXP_PULL_INTERVAL=180
OXP_TOPOLOGY_URL=http://192.168.201.205:8088/SDX-LC/1.0.0/topology
OXP_PULL_TOPOLOGY_INTERVAL=180
OXP_CONNECTION_URL=http://192.168.201.205:8088/SDX-LC/1.0.0/connection
OXP_LIST_CONNECTIONS_URL=http://192.168.201.205:8088/SDX-LC/1.0.0/connections

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to the L2VPN provisioning API, the same endpoint used for creating L2VPNs is used to list all L2VPNs. Thus, I believe we could have the same env var here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I was wrong, the endpoint for creating l2vpn is "1.0.0/connection", while the one list all connections is "1.0.0/connections"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was the earlier endpoint, but when we finished the spec for L2VPN provisioning API, we refactor the Kytos SDX Napp to support the new endpoints, which are both (for creating and listing) /l2vpn/1.0

see more: https://github.com/atlanticwave-sdx/sdx-end-to-end-tests/blob/e98cce8412ee820529187d0fbf478adbdcf63493/env/ampath-lc.env#L7

OXP_PULL_CONNECTIONS_INTERVAL=180
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies = [
"connexion[swagger-ui] == 2.14.2",
"asgiref >= 3.7.2",
"pymongo > 3.0",
"sdx-datamodel @ git+https://github.com/atlanticwave-sdx/datamodel@v3.2.1",
"sdx-datamodel @ git+https://github.com/atlanticwave-sdx/datamodel@v3.2.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Cong, can you please confirm this change? like using a older version?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow datamodel's most recent tag is 3.2.0 (https://github.com/atlanticwave-sdx/datamodel/tags). I'll check with Yufeng to make sure tags are consistent.

]

[project.optional-dependencies]
Expand Down
6 changes: 5 additions & 1 deletion sdx_lc/handlers/sdx_controller_msg_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,12 @@ def process_sdx_controller_json_msg(self, msg):

if "link" in msg_json and ("endpoints" in msg_json["link"]):
service_id = msg_json.get("service_id")
if not service_id:
self.logger.info(f"Connection did not include service_id. Ignored.")
return

connection = msg_json.get("link")
self.db_instance.add_key_value_pair_to_db(self.message_id, connection)
self.db_instance.add_key_value_pair_to_db(service_id, connection)
self.logger.info("Save to database complete.")
self.logger.info("Message ID:" + str(self.message_id))
self.message_id += 1
Expand Down
108 changes: 108 additions & 0 deletions sdx_lc/jobs/pull_connection_changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import json
import logging
import os.path
import sys
import time

import requests
from sdx_datamodel.constants import Constants, MessageQueueNames

# append abspath, so this file can import other modules from parent directory
sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
)

from messaging.rpc_queue_producer import RpcProducer
from utils.db_utils import DbUtils

SDXLC_DOMAIN = os.environ.get("SDXLC_DOMAIN")
OXP_LIST_CONNECTIONS_URL = os.environ.get("OXP_LIST_CONNECTIONS_URL")
OXP_PULL_CONNECTIONS_INTERVAL = os.environ.get("OXP_PULL_CONNECTIONS_INTERVAL")
PUB_QUEUE = MessageQueueNames.OXP_UPDATE
logger = logging.getLogger(__name__)


def main():
db_instance = DbUtils()
db_instance.initialize_db()
process_oxp_connections(db_instance)


# Periodically pull l2vpn (connection) status from OXP, and handle status change.
# Possible l2vpn status are:
# “up” if the L2VPN is operational,
# “down” if the L2VPN is not operational due to topology issues/lack of path, or endpoints being down,
# “error” when there is an error with the L2VPN,
# “under provisioning” when the L2VPN is still being provisioned by the OXPs,
# “maintenance” when the L2VPN is being affected by a network maintenance.
def process_oxp_connections(db_instance):
while True:
time.sleep(int(OXP_PULL_CONNECTIONS_INTERVAL))

try:
response = requests.get(OXP_LIST_CONNECTIONS_URL)
Comment thread
congwang09 marked this conversation as resolved.
Outdated
connections = response.content
except (requests.ConnectionError, requests.HTTPError):
logger.debug("Error connecting to OXP...")
Comment thread
congwang09 marked this conversation as resolved.
Outdated
continue

if not response.ok:
continue
Comment thread
congwang09 marked this conversation as resolved.
Outdated

logger.debug("Received connections from OXP.")

try:
connections_json = response.json()
except ValueError:
logger.debug("Cannot parse connections, invalid JSON.")
continue

if not connections_json:
logger.debug("No connections yet.")
continue

for service_id, connection in connections_json.items():
# Fetch existing connection from DB
existing_connection = db_instance.get_value_by_key(service_id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cong, another doubt arrived while reviewing the SDX-COntroller side: can you please double check if we really want to maintain a database entry in the SDX-LC? It will make things a bit more complicated to make sure the L2VPNs are removed when removed, inserted, updated, etc. My question is: what is the actual advantage of keeping state on the SDX-LC (at least for l2vpns)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Italo, the db in LC is to compare existing l2vpn's "status" with new "status" pulled from OXP, and only send specific l2vpn to SDX controller if status changes. If without database, we will need to periodically forward a long list of all l2vpn to SDX controller, and let SDX controller decide if status has changed. I think this would not be very efficient. I'm open to other options if you have better ideas.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right Cong. From the efficiency perspective, this would save a few messages and bytes from being exchanged. However, when we designed the SDX-LC one of the ideas was not to maintain state or having much intelligence on it, so that we could have one central entity where the actual processing would happen (and then we can concentrate the decision making process and intelligence).

If we are to change this idea, that is fine for me. However, in that case, you have to delete the L2VPN from SDX-LC local database upon receiving a removal request from sdx-controller, right? can you please double check that?

elif msg_json.get("operation") == "delete":
evc_id = msg_json.get("evc_id")
try:
dest_url = urljoin(
str(OXP_CONNECTION_URL).rstrip("/") + "/", evc_id
)
self.logger.info(f"Sending DELETE request to URL: {dest_url}")
oxp_response = requests.delete(
dest_url,
json=connection,
auth=(OXP_USER, OXP_PASS),
)
except Exception as e:
self.logger.error(f"Error on DELETE {OXP_CONNECTION_URL}: {e}")
self.logger.info(
"Check your configuration and make sure OXP service is running."
)
self.logger.info(
f"Status from OXP: {oxp_response} - {oxp_response.text}"
)
self.send_conn_response_to_sdx_controller(
service_id, msg_json["operation"], oxp_response
)

Also, the pull L2VPN from OXP routine should check the local DB for missing L2VPNs and notify the Controller that missing L2VPN, right? Currently, this does not seems covered on the routine

Finally, for "alien" L2VPNs (i.e., the ones returned by OXP but not found on the local DB) we also should notify the SDX-Controller, right? currently, the routine is just ignoring them https://github.com/atlanticwave-sdx/sdx-lc/pull/203/changes#diff-92d2d9f91fcf0945f76b15e8823c1b9cc0b5106b66ea27f2c54d73caa69ff713R67


if not existing_connection:
logger.debug(f"New connection {service_id}, ignored")
continue

try:
existing_connection_json = json.loads(existing_connection)
except ValueError:
logger.debug(f"Invalid JSON in DB for {service_id}")
continue

existing_connection_status = (
existing_connection_json.get("status")
if existing_connection_json
else None
)
new_status = connection.get("status")

if existing_connection_status == new_status:
logger.debug(f"Status unchanged for {service_id}")
continue

existing_connection_json["status"] = new_status
logger.debug(
Comment thread
congwang09 marked this conversation as resolved.
Outdated
f"Status change for {service_id}: "
f"{existing_connection_status} changed to {new_status}"
)
db_instance.add_key_value_pair_to_db(service_id, existing_connection_json)
rpc_msg = {
"lc_domain": SDXLC_DOMAIN,
"msg_type": "oxp_conn_status_change",
"service_id": service_id,
"existing_status": existing_connection_status,
"new_status": new_status,
}
rpc_producer = RpcProducer(5, "", PUB_QUEUE)
rpc_producer.call(json.dumps(rpc_msg))
rpc_producer.stop()


if __name__ == "__main__":
main()
Comment thread
congwang09 marked this conversation as resolved.
8 changes: 4 additions & 4 deletions sdx_lc/jobs/pull_topo_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

OXPO_USER = os.environ.get("OXPO_USER", None)
OXPO_PASS = os.environ.get("OXPO_PASS", None)
OXP_PULL_URL = os.environ.get("OXP_PULL_URL")
OXP_PULL_INTERVAL = os.environ.get("OXP_PULL_INTERVAL")
OXP_TOPOLOGY_URL = os.environ.get("OXP_TOPOLOGY_URL")
OXP_PULL_TOPOLOGY_INTERVAL = os.environ.get("OXP_PULL_TOPOLOGY_INTERVAL")
PUB_QUEUE = MessageQueueNames.OXP_UPDATE
logger = logging.getLogger(__name__)

Expand All @@ -32,7 +32,7 @@ def main():

def process_domain_controller_topo(db_instance):
while True:
time.sleep(int(OXP_PULL_INTERVAL))
time.sleep(int(OXP_PULL_TOPOLOGY_INTERVAL))
latest_topology_exists = False
latest_topology = db_instance.read_from_db(Constants.LATEST_TOPOLOGY)

Expand All @@ -55,7 +55,7 @@ def process_domain_controller_topo(db_instance):
logger.debug("Latest topology does not exist")

try:
response = requests.get(OXP_PULL_URL, auth=(OXPO_USER, OXPO_PASS))
response = requests.get(OXP_TOPOLOGY_URL, auth=(OXPO_USER, OXPO_PASS))
pulled_topology = response.content
except (requests.ConnectionError, requests.HTTPError):
logger.debug("Error connecting to domain controller...")
Expand Down