-
Notifications
You must be signed in to change notification settings - Fork 48
Refactor base driver #273
Comments
ping @ktbyers @mirceaulinic |
Yes, I don't really like either pattern. We are starting to get a lot of indirection in our code _wrapper, _open, et cetera. Which makes reading the code/understanding the code overly hard (for sometimes minimal reductions in number of lines of code). I wonder if there is some other pattern we could start to use more of like decorators (or some other pattern that is better). I probably need a bigger example of your proposal (on the face of it, I am not seeing why we would want to do it). Either that or I need more coffee which is entire possible. |
At a glance, I don't really like it either. I'm echoing Kirk's argument from above:
David: |
It's just another way of implementing the "adapter pattern". The advantage over decorators is that decorators need to be defined explicitly while this is controlled from the parent class and the adapters only have to implement the "napalm protocol/interface". It's no different from how other protocols work so you can make a class behave like a dictionary, provide a context manager, etc... but instead of implementing
I think we need a minimum set of log messages common to every driver. For example, if I am running
With only these three logging messages I can easily know whose fault is if something doesn't break and this is easily implementable with something like this:
We can still provide more detailed logging if the driver requires it or if it's useful inside Also note that for the getters it'd be fairly trivial to build those methods dynamically as all of them would have the same pattern.
it's not about reducing code, it's about ensuring consistency and having control. If tomorrow we want to change that previous workflow and retry N times we can do it in the parent class without modifying the drivers. Or if we want to do some cleanup or recovery attempt after failure we just define the "protocol" (i.e. we need
Check the adapter pattern. Right now we implement what other languages call interfaces which does the job but I really think the adapter pattern would be better as it would give us more control. If you check the adapter pattern, the base driver would be what the pattern calls the client. |
Not sure a constraint like that would be a great idea. Anyway, I see what you mean, but overall I tend to believe this aims way too far just for logging. I think it adds an unnecessary level of indirection. I was thinking about something very simple: I will take for example a random not-very-long method from napalm-ios. Currently implemented like: @staticmethod
def _create_tmp_file(config):
"""Write temp file and for use with inline config and SCP."""
tmp_dir = tempfile.gettempdir()
rand_fname = py23_compat.text_type(uuid.uuid4())
filename = os.path.join(tmp_dir, rand_fname)
with open(filename, 'wt') as fobj:
fobj.write(config)
return filename I would add just a couple of debug logs: @staticmethod
def _create_tmp_file(config):
"""Write temp file and for use with inline config and SCP."""
log.debug('Creating temporary file, under:')
tmp_dir = tempfile.gettempdir()
log.debug(tmp_dir)
rand_fname = py23_compat.text_type(uuid.uuid4())
filename = os.path.join(tmp_dir, rand_fname)
log.debug('Temporary file absolute path:')
log.debug(rand_fname)
log.debug('Writing contents to the temp file')
with open(filename, 'wt') as fobj:
fobj.write(config)
return filename Maybe not that verbose, but I think you see what you mean. Looking into the If we were to try catching all of these under napalm-base, I would say it might be overkill & the implementation would take extremely long, while a simple PR adding the necessary logs per driver would be just straight forward. |
It's not just for logging. It's about controlling the workflow and only requiring the drivers to implement smaller primitives. Primitives that do only one single thing so the flow can be controlled elsewhere. We are talking about logging now because it was the trigger of the conversation but imagine for a second we were using the def get_bgp_neighbors(self):
logger.debug("calling get_bgp_neighbors")
+ if not self._is_open(): # this would be the primitive that self.is_alive() implements and would return always true if not implemented
+ logger.error("connection with the device is not open")
+ raise NapalmConnectionClosed("connection with the device is not open")
logger.debug("gathering data for get_bgp_neighbors")
try:
data = self._get_bgp_neighbors_data(self)
... (rest as my previous comment) Voila! All drivers now verify the connection to the device is alive before actually trying anything and return a meaningful error and a consistent exception.
Ok, you have solved 0.1% of the problem. What about the other 99.9%? How do we make sense of the logs if every driver implements logs wherever they want and with their own format? I am fine scrapping this idea but I want to make sure the idea and patterns are understood and this is just not scrapped because people didn't really understand it. |
This is something that I have been thinking for some time and now #272 gave me a reason to actually try to sell it.
The idea would be to do, instead of what we do today do, something like this:
Used the logging example from #272 but this could help us implement workflows more consistently across platforms. Something like:
Completely made up in case it wasn't obvious but the idea is that the base driver could implement the workflow, try error recoveries, etc. and the drivers could just implement small functions that do very specific things.
This depends on us agreeing to reunify all the drivers. Otherwise this is probably going to be too hard to implement, or rather it's going to be too work.
The text was updated successfully, but these errors were encountered: