Skip to content
Snippets Groups Projects
Commit 0318858e authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Add extra items info to hosts json model

Linked items are by default returned as a list of ICS id.
When recursive is True, a list of {ics_id, serial_number, stack_member}
is returned instead.

Items are always returned sorted by stack_member or ics_id when
stack_member is null.

Allow to add the serial_number and stack_member to the Ansible variables
in csentry dynamic inventory. Useful for switches.

JIRA INFRA-1111 #action In Progress
parent 30670931
No related branches found
No related tags found
No related merge requests found
...@@ -17,6 +17,7 @@ import urllib.parse ...@@ -17,6 +17,7 @@ import urllib.parse
import elasticsearch import elasticsearch
import sqlalchemy as sa import sqlalchemy as sa
from enum import Enum from enum import Enum
from operator import itemgetter, attrgetter
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
from sqlalchemy.orm import validates from sqlalchemy.orm import validates
...@@ -1144,7 +1145,16 @@ class Host(CreatedMixin, SearchableMixin, db.Model): ...@@ -1144,7 +1145,16 @@ class Host(CreatedMixin, SearchableMixin, db.Model):
"device_type": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, "device_type": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
"model": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, "model": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
"description": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, "description": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
"items": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, "items": {
"properties": {
"ics_id": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
"serial_number": {
"type": "text",
"fields": {"keyword": {"type": "keyword"}},
},
"stack_member": {"type": "byte"},
}
},
"interfaces": { "interfaces": {
"properties": { "properties": {
"id": {"enabled": False}, "id": {"enabled": False},
...@@ -1309,7 +1319,12 @@ class Host(CreatedMixin, SearchableMixin, db.Model): ...@@ -1309,7 +1319,12 @@ class Host(CreatedMixin, SearchableMixin, db.Model):
"device_type": str(self.device_type), "device_type": str(self.device_type),
"model": self.model, "model": self.model,
"description": self.description, "description": self.description,
"items": [str(item) for item in self.items], "items": [
str(item)
for item in sorted(
self.items, key=attrgetter("stack_member", "ics_id")
)
],
"interfaces": [str(interface) for interface in self.interfaces], "interfaces": [str(interface) for interface in self.interfaces],
"ansible_vars": self.ansible_vars, "ansible_vars": self.ansible_vars,
"ansible_groups": [str(group) for group in self.ansible_groups], "ansible_groups": [str(group) for group in self.ansible_groups],
...@@ -1319,6 +1334,19 @@ class Host(CreatedMixin, SearchableMixin, db.Model): ...@@ -1319,6 +1334,19 @@ class Host(CreatedMixin, SearchableMixin, db.Model):
# Replace the list of interface names by the full representation # Replace the list of interface names by the full representation
# so that we can index everything in elasticsearch # so that we can index everything in elasticsearch
d["interfaces"] = [interface.to_dict() for interface in self.interfaces] d["interfaces"] = [interface.to_dict() for interface in self.interfaces]
# Add extra info in items
# stack_member can be None, so we have to sort on ics_id as fallback
d["items"] = sorted(
[
{
"ics_id": item.ics_id,
"serial_number": item.serial_number,
"stack_member": item.stack_member,
}
for item in self.items
],
key=itemgetter("stack_member", "ics_id"),
)
return d return d
......
...@@ -1368,7 +1368,9 @@ def test_get_hosts_with_no_model(client, host_factory, readonly_token): ...@@ -1368,7 +1368,9 @@ def test_get_hosts_with_no_model(client, host_factory, readonly_token):
assert response.get_json()[0]["model"] is None assert response.get_json()[0]["model"] is None
def test_get_hosts_recursive(client, host_factory, interface_factory, readonly_token): def test_get_hosts_recursive_interfaces(
client, host_factory, interface_factory, readonly_token
):
# Create some hosts with interfaces # Create some hosts with interfaces
host1 = host_factory() host1 = host_factory()
interface11 = interface_factory(name=host1.name, host=host1) interface11 = interface_factory(name=host1.name, host=host1)
...@@ -1400,6 +1402,57 @@ def test_get_hosts_recursive(client, host_factory, interface_factory, readonly_t ...@@ -1400,6 +1402,57 @@ def test_get_hosts_recursive(client, host_factory, interface_factory, readonly_t
assert rinterface21["network"] == interface21.network.vlan_name assert rinterface21["network"] == interface21.network.vlan_name
def test_get_hosts_recursive_items(client, item_factory, host_factory, readonly_token):
host1 = host_factory()
item11 = item_factory(ics_id="AAA001", host_id=host1.id, stack_member=1)
item12 = item_factory(ics_id="AAA002", host_id=host1.id, stack_member=0)
host2 = host_factory()
item21 = item_factory(ics_id="AAB001", host_id=host2.id)
item22 = item_factory(ics_id="AAB002", host_id=host2.id)
# Without recursive, we only get the ics_id of the items
response = get(client, f"{API_URL}/network/hosts", token=readonly_token)
assert response.status_code == 200
assert len(response.get_json()) == 2
rhost1, rhost2 = response.get_json()
# items are sorted by stack_member
assert rhost1["items"] == ["AAA002", "AAA001"]
# or by ics_id when stack_member is None
assert rhost2["items"] == ["AAB001", "AAB002"]
# With recursive, items are expanded
response = get(
client, f"{API_URL}/network/hosts?recursive=true", token=readonly_token
)
assert response.status_code == 200
assert len(response.get_json()) == 2
rhost1, rhost2 = response.get_json()
assert len(rhost1["items"]) == 2
# Items shall be sorted by stack_member
ritem11, ritem12 = rhost1["items"]
assert ritem11 == {
"ics_id": item12.ics_id,
"serial_number": item12.serial_number,
"stack_member": 0,
}
assert ritem12 == {
"ics_id": item11.ics_id,
"serial_number": item11.serial_number,
"stack_member": 1,
}
assert len(rhost2["items"]) == 2
# or ics_id when no stack_member
ritem21, ritem22 = rhost2["items"]
assert ritem21 == {
"ics_id": item21.ics_id,
"serial_number": item21.serial_number,
"stack_member": None,
}
assert ritem22 == {
"ics_id": item22.ics_id,
"serial_number": item22.serial_number,
"stack_member": None,
}
def test_create_host(client, device_type_factory, user_token): def test_create_host(client, device_type_factory, user_token):
device_type = device_type_factory(name="Virtual") device_type = device_type_factory(name="Virtual")
# check that name and device_type are mandatory # check that name and device_type are mandatory
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment