-
Notifications
You must be signed in to change notification settings - Fork 48
Added recorder and an example (to be removed) #296
Conversation
@ubaumann @mirceaulinic @ktbyers thoughts? |
My findings so far:
With the files and the count number, the order of commands can't be changed. So, every workflow needs a separate record. I would like to have a more flexible way. For example creating a YAML file with the function name and the arguments, so the the recording can be reused. I know the file could get big.
Advantages from my point of view:
Recordings make it easy to test against different OS versions |
napalm_base/recorder.py
Outdated
logger.debug("Recording {}".format(func.__name__)) | ||
r = func(*args, **kwargs) | ||
filename = "{}.{}".format(func.__name__, cls.current_count) | ||
with open(os.path.join(cls.path, filename), 'w') as f: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should scream as a resource leakage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this is saving a file under the Python sys paths (where the lib is installed) - not good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should scream as a resource leakage
Care to elaborate?
Also, this is saving a file under the Python sys paths (where the lib is installed) - not good.
path is specified by the user. Not sure what you mean.
napalm_base/recorder.py
Outdated
def record(cls, func, *args, **kwargs): | ||
logger.debug("Recording {}".format(func.__name__)) | ||
r = func(*args, **kwargs) | ||
filename = "{}.{}".format(func.__name__, cls.current_count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd probably use the FQDN (the host
variable sent during init) rather than a spin number, so you can have a better understanding on what commands have been executed where.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path is specified by the user via optional_arg["recorder_path"]
, if you want to use hostname, use it. You are not limited by anyone making any decissions for you. The code you are pointing out is just a deterministic way of finding the order of the calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda like the idea, but if I'd compare the benefits vs. complexity + risks and environment exposure, I find there's not much real value added.
There's also the long-vehiculated argument: this should probably happen in a higher level tool, napalm is just a library :]
Yes, I believe this is way, way, beyond the goals of NAPALM - as @dbarrosop likes to say everytime, "napalm is a library, it's not a tool.", so this implementation, from any perspective, I believe it doesn't really fit into this definition. |
I agree. That's certainly an easy bug to fix.
That's the point. This is a recorder/replayer.
Complexity, less than what we have today. Benefits of having this between each driver and the underlying class (netmiko, pyeapi, etc):
An alternative to the "pass" behavior would be to call the class directly without the wrapper so the default behavior wouldn't change at all. So on the drivers we would have:
Or something like that |
My take:
IMHO unless this introduced an unnecessary amount of work and future burden I see it as clear net positive. |
@itdependsnetworks Can you elaborate on this statement?
I think we should use probably use JSON or YAML to save data instead of pickle. I like the general idea. |
I'm simply highlighting he fact that that command will never be part of any standard getters. It would be nice to have a standard way to say the raw response no matter what you were running |
Problem with YAML/JSON is that it becomes a problem when the method returns pythons objects. For example, a method might return |
If we use Pickle, then we won't be able to accept data from anyone we don't trust...as they could totally mess us over (like embed system calls inside the pickle file) and we wouldn't be able to easily inspect what was sent to us beforehand. Booleans and None are not really a problem (objects including Exceptions are a problem). Does it break how you were planning on using it? I was thinking of it as more of capturing data that could be added to unit tests (as opposed to replacing mocked devices).
|
As discussed via other channels. I think you are right and I will look into that. The main issue is that not all calls might return structured but some "bare" object like a string or an Exception but that can be treated.
That is very much correct so we will do two things:
|
…ed and with which lib versions
Changes Unknown when pulling 507a46e on recorder into ** on develop**. |
Regarding the metadata, now it adds a
|
Changes Unknown when pulling b8d1ccb on recorder into ** on develop**. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a comment regarding the dependency. Please let's discuss before pushing this further.
@@ -2,3 +2,4 @@ jtextfsm | |||
jinja2 | |||
netaddr | |||
pyYAML | |||
camel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are anyway adding another dependency for serialization, why not use something widely adopted like msgpack?
Whatever would be the extra package, I dislike very much the idea of having a dependency for a feature I know for sure I'll never use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"MessagePack is an efficient binary serialization format. It's like JSON. but fast and small."
So a few reasons:
- We don't want binary, we want something secure that humans can look at.
- Camel is is a tiny library that builds on top of pyyaml, which we already have.
msgpack
is huge and include even more C extensions.
@ktbyers @mirceaulinic latest update replaces pickle with I tried pyyaml pickle-like behavior but it turns out is as unsafe as I recommend reading this if you have the time/energy: https://eev.ee/blog/2015/10/15/dont-use-pickle-use-camel/ If you are happy with this I will remove all the testing stuff I added and merge. Then I will quickly integrate it with the |
@dbarrosop - you may have missed my review. I disagree on using an obscure library that gained less interest than naplam (i.e. https://github.com/eevee/camel has exactly 7 followers), rather than a very well known and widely adopted msgpack (also very well know for its speed). Also, it's very bizzare that the sole resource I was able to find about this camel is the blog post you pointed out... written by its maintainer and one of the 4 contributors. |
Are we measuring now usefulness by number of followers? Doesn't make much sense IMHO.
I can write myself what camel does as it's just a framework on top of pyyaml, which seems unnecessary to write as someone else did it already, but as I mentioned in the other comment, |
As discussed in slack, will rewrite without dependencies. |
This PR introduces a class that can sit between the driver and the underlying library (pyeapi, netmiko, pyez, etc. see napalm-automation/napalm-eos/pull/173).
The "middleware" has three modes:
pass
. Does nothing. TheRecorder
will just proxy the calls to the underlying library.record
. The interactions between the driver and the underlying library are proxied and recorded.replay
. The interactions between the driver and the underlying library are interrupted. Instead, theRecorder
will pickle a previous recording and replay it.Example, in the following snippet we are running the same code three times testing all modes. Note that in the "replay" mode at the end I set wrong data to prove that there is no connection with the real device.
TODO: