Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
47 changes: 43 additions & 4 deletions nsot/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,32 +662,71 @@ def get_serializer_class(self):
return serializers.InterfaceUpdateSerializer
if self.request.method == 'PATCH':
return serializers.InterfacePartialUpdateSerializer

return self.serializer_class

@detail_route(methods=['get'])
def addresses(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return a list of addresses for this Interface."""
interface = self.get_resource_object(pk, site_pk)
addresses = interface.addresses.all()

return self.list(request, queryset=addresses, *args, **kwargs)

@detail_route(methods=['get'])
def assignments(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return a list of information about my assigned addresses."""
interface = self.get_resource_object(pk, site_pk)
assignments = interface.assignments.all()

return self.list(request, queryset=assignments, *args, **kwargs)

@detail_route(methods=['get'])
def networks(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return all the containing Networks for my assigned addresses."""
interface = self.get_resource_object(pk, site_pk)

return self.list(request, queryset=interface.networks, *args, **kwargs)

@detail_route(methods=['get'])
def parent(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return the parent of this Interface."""
interface = self.get_resource_object(pk, site_pk)
parent = interface.parent
if parent is not None:
pk = interface.parent_id
else:
pk = None
return self.retrieve(request, pk, site_pk, *args, **kwargs)

@detail_route(methods=['get'])
def ancestors(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return all the ancestors of this Interface."""
interface = self.get_resource_object(pk, site_pk)
return self.list(request, queryset=interface.get_ancestors(), *args, **kwargs)

@detail_route(methods=['get'])
def children(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return all the immediate children of this Interface."""
interface = self.get_resource_object(pk, site_pk)
return self.list(request, queryset=interface.get_children(), *args, **kwargs)

@detail_route(methods=['get'])
def descendants(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return all the descendants of this Interface."""
interface = self.get_resource_object(pk, site_pk)
return self.list(request, queryset=interface.get_descendants(), *args, **kwargs)

@detail_route(methods=['get'])
def siblings(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return all the siblings of this Interface."""
interface = self.get_resource_object(pk, site_pk)
return self.list(request, queryset=interface.get_siblings(), *args, **kwargs)

@detail_route(methods=['get'])
def root(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return the root of the tree this Interface is part of."""
interface = self.get_resource_object(pk, site_pk)
root = interface.get_root()
pk = root.id
return self.retrieve(request, pk, site_pk, *args, **kwargs)

@detail_route(methods=['get'])
def circuit(self, request, pk=None, site_pk=None, *args, **kwargs):
"""Return the Circuit I am associated with"""
Expand Down
53 changes: 51 additions & 2 deletions nsot/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1239,7 +1239,7 @@ class Interface(Resource):
)

parent = fields.ChainedForeignKey(
'nsot.Interface', blank=True, null=True, related_name='children',
'self', blank=True, null=True, related_name='children',
default=None, db_index=True, on_delete=models.PROTECT,
chained_field='device', chained_model_field='device',
verbose_name='Parent', help_text='Unique ID of the parent Interface.',
Expand Down Expand Up @@ -1396,8 +1396,45 @@ def set_addresses(self, addresses, overwrite=False, partial=False):

self.clean_addresses()

def get_ancestors(self):
"""Return all ancestors of an Interface."""
p = self.parent
ancestors = []
while p is not None:
ancestors.append(p)
p = p.parent
ancestor_ids = [a.id for a in ancestors]
return Interface.objects.filter(id__in=ancestor_ids)

def get_children(self):
"""Return the immediate children of an Interface."""
return Interface.objects.filter(parent=self)

def get_descendants(self):
"""Return all the descendants of an Interface."""
s = list(self.get_children())
descendants = []
while len(s) > 0:
top = s.pop()
descendants.append(top)
for c in top.get_children():
s.append(c)
descendant_ids = [c.id for c in descendants]
return Interface.objects.filter(id__in=descendant_ids)

def get_root(self):
"""Return the parent of all ancestors of an Interface."""
root = self
while root.parent is not None:
root = root.parent
return root

def get_siblings(self):
"""Return Interfaces with the same parent and device id as an Interface."""
return Interface.objects.filter(parent=self.parent, device=self.device).exclude(id=self.id)

def get_assignments(self):
"""Return a list of informatoin about my assigned addresses."""
"""Return a list of information about my assigned addresses."""
return [a.to_dict() for a in self.assignments.all()]

def get_addresses(self):
Expand Down Expand Up @@ -1473,13 +1510,25 @@ def clean_device_hostname(self, device):
"""Extract hostname from device"""
return device.hostname

def clean_parent(self, parent):
if parent is None:
return parent
if parent.device_hostname != self.device_hostname:
raise exc.ValidationError({
'parent': "Parent's device does not match device with host name %r"%self.device_hostname
})
return parent


def clean_fields(self, exclude=None):
self.site_id = self.clean_site(self.site_id)
self.name = self.clean_name(self.name)
self.type = self.clean_type(self.type)
self.speed = self.clean_speed(self.speed)
self.mac_address = self.clean_mac_address(self.mac_address)
self.device_hostname = self.clean_device_hostname(self.device)
self.parent = self.clean_parent(self.parent)


def save(self, *args, **kwargs):
# We don't want to validate unique because we want the IntegrityError
Expand Down
132 changes: 132 additions & 0 deletions tests/api_tests/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def test_creation(site, client):
dev_resp = client.create(dev_uri, hostname='foo-bar1')
dev = get_result(dev_resp)

dev_resp1 = client.create(dev_uri, hostname='foo-bar2')
dev1 = get_result(dev_resp1)

net_resp = client.create(net_uri, cidr='10.1.1.0/24')
net = get_result(net_resp)

Expand All @@ -56,6 +59,13 @@ def test_creation(site, client):

assert_created(ifc1_resp, ifc1_obj_uri)

# Verify that creating a device with parent as
# ifc1 but device as foo-bar2 will cause error
assert_error(
client.create(ifc_uri, device=dev1['id'], name='eth0.1', parent_id=ifc1['id']),
status.HTTP_400_BAD_REQUEST
)

# Make sure MAC is None
assert ifc1['mac_address'] is None

Expand Down Expand Up @@ -83,6 +93,128 @@ def test_creation(site, client):
assert_success(client.get(ifc_uri), expected)


def test_tree_traversal(site, client):
"""Test basic creation of an Interface."""
ifc_uri = site.list_uri('interface')
dev_uri = site.list_uri('device')

dev_resp = client.create(dev_uri, hostname='foo-bar1')
dev = get_result(dev_resp)

dev_resp1 = client.create(dev_uri, hostname='foo-bar2')
dev1 = get_result(dev_resp1)

ifc1_resp = client.create(
ifc_uri, device=dev['id'], name='eth0', parent_id=None,
mac_address=None,
)
ifc1 = get_result(ifc1_resp)
ifc1_obj_uri = site.detail_uri('interface', id=ifc1['id'])

assert_created(ifc1_resp, ifc1_obj_uri)

# Create another interface with ifc1 as parent
ifc2_resp = client.create(
ifc_uri, device=dev['id'], name='eth0.0', parent_id=ifc1['id'],
mac_address=None
)
ifc2 = get_result(ifc2_resp)
ifc2_obj_uri = site.detail_uri('interface', id=ifc2['id'])

assert_created(ifc2_resp, ifc2_obj_uri)

# Create another interface with ifc2 as parent
ifc3_resp = client.create(
ifc_uri, device = dev['id'], name='eth0.1', parent_id=ifc2['id'],
mac_address = None
)
ifc3 = get_result(ifc3_resp)
ifc3_obj_uri = site.detail_uri('interface', id = ifc3['id'])

assert_created(ifc3_resp, ifc3_obj_uri)

# Create another interface with ifc2 as parent
ifc4_resp = client.create(
ifc_uri, device = dev['id'], name='eth0.2', parent_id=ifc2['id'],
mac_address = None
)
ifc4 = get_result(ifc4_resp)
ifc4_obj_uri = site.detail_uri('interface', id = ifc4['id'])

assert_created(ifc4_resp, ifc4_obj_uri)

# Create another interface with ifc2 as parent
ifc5_resp = client.create(
ifc_uri, device = dev['id'], name='eth0.3', parent_id=ifc2['id'],
mac_address = None
)

ifc5 = get_result(ifc5_resp)
ifc5_obj_uri = site.detail_uri('interface', id = ifc5['id'])

assert_created(ifc5_resp, ifc5_obj_uri)

ifc6_resp = client.create(
ifc_uri, device = dev1['id'], name='eth0.4', parent_id=None,
mac_address = None
)

ifc6 = get_result(ifc6_resp)
ifc6_obj_uri = site.detail_uri('interface', id = ifc6['id'])

assert_created(ifc6_resp, ifc6_obj_uri)

ifc7_resp = client.create(
ifc_uri, device = dev1['id'], name='eth0.5', parent_id=None,
mac_address = None
)

ifc7 = get_result(ifc7_resp)
ifc7_obj_uri = site.detail_uri('interface', id = ifc7['id'])

assert_created(ifc7_resp, ifc7_obj_uri)

# test Ancestors by calling it on ifc3
expected = [ifc1, ifc2]
uri = reverse('interface-ancestors', args = (site.id, ifc3['id']))
assert_success(client.retrieve(uri), expected)

# test children by calling it on ifc2
expected = [ifc3, ifc4, ifc5]
uri = reverse('interface-children', args = (site.id, ifc2['id']))
assert_success(client.retrieve(uri), expected)

# test descendants by calling it on ifc1
expected = [ifc2, ifc3, ifc4, ifc5]
uri = reverse('interface-descendants', args = (site.id, ifc1['id']))
assert_success(client.retrieve(uri), expected)

# test siblings by calling it on ifc3
expected = [ifc4, ifc5]
uri = reverse('interface-siblings', args = (site.id, ifc3['id']))
assert_success(client.retrieve(uri), expected)

# test root by calling it on ifc4
expected = ifc1
uri = reverse('interface-root', args=(site.id, ifc4['id']))
assert_success(client.retrieve(uri), expected)

# test that root of ifc1 is ifc1
expected = ifc1
uri = reverse('interface-root', args=(site.id, ifc1['id']))
assert_success(client.retrieve(uri), expected)

# test parent by calling it on ifc5
expected = ifc2
uri = reverse('interface-parent', args=(site.id, ifc5['id']))
assert_success(client.retrieve(uri), expected)

# test sibling for interfaces with None as parent and attached to different devices
expected = [ifc7]
uri = reverse('interface-siblings', args=(site.id, ifc6['id']))
assert_success(client.retrieve(uri), expected)


def test_creation_with_addresses(site, client):
"""Test creating an Interface w/ addresses."""
ifc_uri = site.list_uri('interface')
Expand Down
44 changes: 44 additions & 0 deletions tests/model_tests/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,50 @@ def test_creation(device):
iface.save()


def test_tree_methods(device):
iface = models.Interface.objects.create(
device = device, name = 'eth0'
)
iface1 = models.Interface.objects.create(
device = device, name = 'eth0.0', parent = iface
)
iface2 = models.Interface.objects.create(
device = device, name = 'eth0.1', parent = iface
)
iface3 = models.Interface.objects.create(
device = device, name = 'eth0.2', parent = iface1
)
iface4 = models.Interface.objects.create(
device = device, name = 'eth0.3', parent = iface
)
assert iface1.parent.id is iface.id
assert iface3.get_root().id is iface.id

children = [x.id for x in iface.get_children()]
expected = [iface1.id, iface2.id, iface4.id]
children.sort()
expected.sort()
assert children == expected

descendants = [x.id for x in iface.get_descendants()]
expected = [iface1.id, iface2.id, iface3.id, iface4.id]
expected.sort()
descendants.sort()
assert descendants == expected

ancestors = [x.id for x in iface3.get_ancestors()]
expected = [iface1.id, iface.id]
expected.sort()
ancestors.sort()
assert ancestors == expected

siblings = [x.id for x in iface4.get_siblings()]
expected = [iface1.id, iface2.id]
siblings.sort()
expected.sort()
assert siblings == expected


def test_speed(device):
"""Test interface speed."""
iface = models.Interface.objects.create(device=device, name='eth0')
Expand Down