Replies: 3 comments 3 replies
-
Hmm, one thing I'd like to see added to this is class PathDeploymentManager:
"""
Manager for deploying paths.
"""
def deploy_static_path(self, context, path_spec):
session = context.get_session()
vlan_reservations = self.get_vlan_reservations(context, path_spec)
flows = self.generate_flows(context, path_spec["cookie"], vlan_reservations)
context.call("FlowManager", "install_flows", flows)
deployed_path = {
"path_spec": path_spec,
"reserved_vlans": reserved_vlans,
}
db.deployed_paths.insert_one(deployed_path, session = session)
context.call_on_success("EventManager", "push", "path_installed", deployed_path)
def get_vlan_reservations(self, context, path_spec):
vlan_reservations = {}
for link in path_spec["links"]:
vlan = context.call("LinkManager", "reserve_vlan", link)
vlan_reservations[link] = vlan
return vlan_reservations
def generate_flows(self, context, cookie, vlan_reservations):
... |
Beta Was this translation helpful? Give feedback.
-
Yes, we should pass either a Nevertheless, to keep in mind:
Although we'll have cases where it'll be beneficial, let's only use MongoDB transactions sparingly at the application level where it's absolutely needed, and then expose a global lock to be passed in the kwargs/context too. MongoDB tends to punish when requiring too much transactions, and there are certain production constraints to be aware, certain times that's unavoidable and reasonable to use. Ultimately, it's up to us to also review from time to time to design and maintain our collections and models to where MongoDB is strong and shines like individual documents (with embedding) and bulk ops in same collection to leverage its engine parallelism execution too. In the short-term we should keep a similar pattern in terms of collections writes and reads. But, as we manage to successfully plan carefully and offload to the DB certain resources when it makes sense and when it's safe to do so, then gradually we can se how much else can be deffered and then for certain operations try to also use transactions, for instance to ensure the topology interface vlans, but again, we always gotta find a balance and be conservative about it, ultimately, MongoDB isn't a relational database at its core, it has bolted on multi docs ACID relational capabilities, but gotta find a balance. |
Beta Was this translation helpful? Give feedback.
-
Any sort sort of pub sub notification should use what we currently have with events. Now, regarding when it completes, we'll get this "for free" in the composability since it'll be just a direct callable, NApps will be able to call the instance class PathManager:
"""
This Manager doesn't exist yet, needs to be implemented
"""
def __init__(self):
...
def find_paths(self, *args, **kwargs):
... class FlowManager:
"""
This Manager doesn't exist yet, needs to be implemented
"""
def __init__(self):
...
def add_flows(self, payload: dict, validate=True, **kwargs):
validate and do_validate(payload, schema, registry=registry)
db_session = kwargs.get('ctx_db_session')
...
return self._install_flows(
"add",
flows_dict,
switches,
reraise_conn=not force,
session=db_session,
)
def add_flows_by_switch(self, payload: dict, validate=True, **kwargs):
validate and do_validate(payload, schema, registry=registry)
db_session = kwargs.get('ctx_db_session')
...
return self._install_flows(
"add",
flows_dict,
switches,
reraise_conn=not force,
session=db_session,
) Usage on pfndr = self.get_napp_pathfinder() # wrapping `self.controller.get_napp_instance("kytos", "pathfinder")` for testability
fmngr = self.get_napp_flow_manager()
_ = pfndr.path_manager.find_paths(payload)
_ = fmngr.flow_manager.add_flows_by_switch(payload) Usage on pfndr = self.get_napp_pathfinder()
fmngr = self.get_napp_flow_manager()
with self.controller.get_db_lock():
with self.db_client.start_session() as session:
with session.start_transaction():
_ = pfndr.path_manager.find_paths(payload)
_ = fmngr.flow_manager.install_flows(payload, session=session) What do you think? Let me know if you have anything else in mind in terms of production usage that hasn't been covered yet. Essentially, it's just extracting a layer, and every manager should be composable and ensuring concurrency safety. Any DB controller or manager that doesn't expose a functionality, then we keep as we've always been augmenting incrementally as needed. |
Beta Was this translation helpful? Give feedback.
-
Goal: This is to remove the request loop back request and its complexity in favor of allowing function calls between NApps as mentioned on this issue discussion.
Requirements on
kytos
coreget_napp_instance(username: str, name: str) -> KytosNApp
onController
KytosNApp
instance from Controller'sself.napps
managers/
from that NApp is supposed to be used, we won't have callable module inspections during runtime, we'll keep it light and follow the Zen of Python. Each NAppmanagers/
andcontrollers/
will be reused by the owning NApp and its API and events, and other client NApps are now encouraged to callmanagers/*
.Requirements on a NApp
controllers/*
managers/*
are expected to be called from other NApps to be common entry points, just so business logic, validations (reuse them from OpenAPI schema), custom exceptions and the inner controllers can be used from there, including eventually to have granular internal concurrency control parametrizing other collection locks and so on, and API routes and event handlers as the server NApp can reuse the managers and map the custom exceptions to API exceptions or event handler errors. Bottom line: We'd be forcing one layer of indirection on demand withmanagers/
to lightly separate the concerns for reusability and code reorganization. A manager doesn't have to support every CRUD operation out of the gate, but as those are needed by other NApps it can be done gradually, and then it gets refactored in the server NApp to be reused in API endpoints and events too.openapi-schema-validator
which is already a sub-dependency ofopenapi-core
https://openapi-schema-validator.readthedocs.io/en/latest/references.html. We don't want double validations like validating in the API and then the manager validating the payload again, so the manager methods allow it to pass a validate=callable, which should be preset with schema=schema, and registry=registry and then the manager would call validate() in a try except, that way it can be used easily from either a direct func call, or from the main.py API endpoint.get_napp_<napp>_<user>()
where it should get thecontroller.get_napp_instance('user', 'napp')
, and then when the NApp instance is needed it would call this method, this is to make it easily to mock the other NApp managers side effects. This is a similar pattern that we use when mocking DB controller methods.For more information about code module organization and their responsibilities (this will be augmented to describe the prior paragraphs about how other NApps can call other NApps too):
https://kytos-ng.github.io/napps/mongodb.html#napps-modules
Beta Was this translation helpful? Give feedback.
All reactions