-
Notifications
You must be signed in to change notification settings - Fork 190
Added a trace property to the traceable interface classes #280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
af4a35e
0ff839f
c8cc839
74b6e53
d020bcc
c670197
55cacaf
bb5ef86
754633c
cc5210d
0a6e0a8
8d69e4a
d8acca0
c8b91e3
997854f
4a0d8ca
e6d2b78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,12 +13,56 @@ | |
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| """ | ||
| from six.moves.urllib.parse import urlsplit | ||
|
|
||
| from pynetbox.core.query import Request | ||
| from pynetbox.core.response import Record, JsonField | ||
| from pynetbox.core.endpoint import RODetailEndpoint | ||
| from pynetbox.models.ipam import IpAddresses | ||
| from pynetbox.models.circuits import Circuits | ||
|
|
||
|
|
||
| class TraceableRecord(Record): | ||
| @property | ||
| def trace(self): | ||
| req = Request( | ||
| key=str(self.id) + "/trace" if not self.url else None, | ||
| base=self.endpoint.url, | ||
| token=self.api.token, | ||
| session_key=self.api.session_key, | ||
| http_session=self.api.http_session, | ||
| ) | ||
| ret = [] | ||
| for (termination_a_data, cable_data, termination_b_data) in req.get(): | ||
| this_hop_ret = [] | ||
| for hop_item_data in (termination_a_data, cable_data, termination_b_data): | ||
| # if not fully terminated then some items will be None | ||
| if not hop_item_data: | ||
| this_hop_ret.append(hop_item_data) | ||
| continue | ||
|
|
||
| url_path = urlsplit(hop_item_data["url"]).path | ||
| if url_path.startswith("/api/dcim/cables"): | ||
| return_obj_class = Cables | ||
| elif url_path.startswith("/api/dcim/front-ports"): | ||
| return_obj_class = FrontPorts | ||
| elif url_path.startswith("/api/dcim/interfaces"): | ||
| return_obj_class = Interfaces | ||
| elif url_path.startswith("/api/dcim/rear-ports"): | ||
| return_obj_class = RearPorts | ||
| else: | ||
| raise NotImplementedError( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this how you would want to handle this situation?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd probably make a dict here that mapped the endpoint name it might encounter with the custom object instead of an if/else tree (really wish switches were a thing in python). e.g. You'll probably want to add some other endpoint/objects you might come across in the traces as well like ConsolePort/ConsoleServerPort and PowerPort/Outlets. I'd probably default to just a simple
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perfect. I'll update to a map and return a |
||
| "unable to unpack item data from endpoint '{}'".format(url_path) | ||
| ) | ||
| this_hop_ret.append( | ||
| return_obj_class(hop_item_data, self.endpoint.api, self.endpoint) | ||
|
raddessi marked this conversation as resolved.
|
||
| ) | ||
|
|
||
| ret.append(this_hop_ret) | ||
|
|
||
| return ret | ||
|
|
||
|
|
||
| class DeviceTypes(Record): | ||
| def __str__(self): | ||
| return self.model | ||
|
|
@@ -80,11 +124,27 @@ class ConnectedEndpoint(Record): | |
| device = Devices | ||
|
|
||
|
|
||
| class Interfaces(Record): | ||
| class Interfaces(TraceableRecord): | ||
| interface_connection = InterfaceConnection | ||
| connected_endpoint = ConnectedEndpoint | ||
|
|
||
|
|
||
| class PowerOutlets(TraceableRecord): | ||
| device = Devices | ||
|
|
||
|
|
||
| class PowerPorts(TraceableRecord): | ||
| device = Devices | ||
|
|
||
|
|
||
| class ConsolePorts(TraceableRecord): | ||
| device = Devices | ||
|
|
||
|
|
||
| class ConsoleServerPorts(TraceableRecord): | ||
| device = Devices | ||
|
|
||
|
|
||
| class RackReservations(Record): | ||
| def __str__(self): | ||
| return self.description | ||
|
|
@@ -99,6 +159,14 @@ class RUs(Record): | |
| device = Devices | ||
|
|
||
|
|
||
| class FrontPorts(Record): | ||
| device = Devices | ||
|
|
||
|
|
||
| class RearPorts(Record): | ||
| device = Devices | ||
|
|
||
|
|
||
| class Racks(Record): | ||
| @property | ||
| def units(self): | ||
|
|
@@ -154,7 +222,26 @@ def __str__(self): | |
|
|
||
| class Cables(Record): | ||
| def __str__(self): | ||
| return "{} <> {}".format(self.termination_a, self.termination_b) | ||
| # populate the terminations to get the full names if they are not already | ||
| try: | ||
| termination_a_name = self.termination_a.name | ||
| except AttributeError: | ||
| try: | ||
| self.termination_a.full_details() | ||
| except TypeError: | ||
| self.termination_a.full_details(self) | ||
| termination_a_name = getattr(self.termination_a, "name", self.termination_a) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like this logic but I'm not sure of a better method. Ideas welcomed
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the goal here? Both of those should be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a change to force each of the returned items along the trace to get fully populated so that when you printed their name in the trace it wasn't just displayed as a EDIT: When I said
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's weird. Do you have an example you can show of the old behavior? Those should be Termination objects, but either way even Record's default
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, with this section of code reverted this is what I'm getting now: So it looks like maybe I was mistaken or confused about only the middle (not the first or last) having the issue, it may be all of the items returned from the trace until they are accessed in some way. You can see here how the info is filled out once the item is accessed directly: You are correct, they are Termination objects. I'm sorry it's been a while since I worked on this so I've forgotten a little of the details. It seems like none of the cable connection object information at all is set until the item is accessed directly for some reason though. I have not dug in to that much yet. EDIT: Actually to be more clear, it seems like the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By moving this to inside the trace function it is muuuuch clearer now. |
||
|
|
||
| try: | ||
| termination_b_name = self.termination_b.name | ||
| except AttributeError: | ||
| try: | ||
| self.termination_b.full_details() | ||
| except TypeError: | ||
| self.termination_b.full_details(self) | ||
| termination_b_name = getattr(self.termination_b, "name", self.termination_b) | ||
|
|
||
| return "{} <> {}".format(termination_a_name, termination_b_name) | ||
|
|
||
| termination_a = Termination | ||
| termination_b = Termination | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,9 +5,9 @@ | |
| from .util import Response | ||
|
|
||
| if six.PY3: | ||
| from unittest.mock import patch | ||
| from unittest.mock import patch, call | ||
| else: | ||
| from mock import patch | ||
| from mock import patch, call | ||
|
|
||
|
|
||
| api = pynetbox.api("http://localhost:8000", token="abc123",) | ||
|
|
@@ -526,11 +526,31 @@ def test_get_circuit(self): | |
| self.assertTrue(isinstance(ret, self.ret)) | ||
| self.assertTrue(isinstance(str(ret), str)) | ||
| self.assertTrue(isinstance(dict(ret), dict)) | ||
| mock.assert_called_with( | ||
| "http://localhost:8000/api/{}/{}/1/".format( | ||
| self.app, self.name.replace("_", "-") | ||
| ), | ||
| headers=HEADERS, | ||
| params={}, | ||
| json=None, | ||
| mock.assert_has_calls( | ||
| [ | ||
| call( | ||
| "http://localhost:8000/api/{}/{}/1/".format( | ||
| self.app, self.name.replace("_", "-") | ||
| ), | ||
| headers=HEADERS, | ||
| params={}, | ||
| json=None, | ||
| ), | ||
| call( | ||
| "http://localhost:8000/api/{}/{}/1/".format( | ||
| "circuits", "circuit-terminations" | ||
| ), | ||
| headers=HEADERS, | ||
| params={}, | ||
| json=None, | ||
| ), | ||
| call( | ||
| "http://localhost:8000/api/{}/{}/1/".format( | ||
| "circuits", "circuit-terminations" | ||
| ), | ||
| headers=HEADERS, | ||
| params={}, | ||
| json=None, | ||
| ), | ||
| ] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also don't love the idea that each call for a cable will now result in 3 calls.. I didn't realize when fixing this test that that is essentially what changed in this test. It doesn't feel right that this needs to happen for each cable returned.. I want to dig in to this because I think this should only be needed for the cables between either the first or last cable returned in a trace since those are the only ones I have ever seen that exhibited the problem of their names not being initialized until you actually interact with the cable object (then it calling it's Alternatively.. we could move this logic to the trace property and have it done there only for cables returned from traces but that feels less consistent somehow. I'm on the fence.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, have a look at the previous comment, the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. I'll also add testing with the next round of changes. |
||
| ) | ||
Uh oh!
There was an error while loading. Please reload this page.