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
124 changes: 80 additions & 44 deletions hydra_base/lib/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,18 @@ def get_attributes_by_id(attr_ids, **kwargs):

def get_template_attributes(template_id, **kwargs):
"""
Get a specific attribute by its ID.
Get all attributes linked to a template via template types.
"""
import hydra_base.lib.template as templatelib
template = templatelib.get_template(template_id, **kwargs)
attr_id_map = {}

try:
attrs_i = db.DBSession.query(Attr).filter(
TemplateType.template_id == template_id).filter(
TypeAttr.type_id == TemplateType.id).filter(
Attr.id == TypeAttr.id).all()

log.debug(attrs_i)
return attrs_i
except NoResultFound:
return None

for tt in template.templatetypes:
for ta in tt.typeattrs:
attr_id_map[ta.attr_id] = ta.attr
attrs = list(attr_id_map.values())
log.info("Attributes linked to template %s: %s", template_id, len(attrs))
return [JSONObject(a) for a in attrs]

def get_attribute_by_name_and_dimension(name, dimension_id=None, network_id=None, project_id=None, **kwargs):
"""
Expand Down Expand Up @@ -1244,51 +1242,89 @@ def get_all_resource_attributes(ref_key, network_id, template_id=None, **kwargs)
"""
Get all the resource attributes for a given resource type in the network.
That includes all the resource attributes for a given type within the network.
For example, if the ref_key is 'NODE', then it will return all the attirbutes
For example, if the ref_key is 'NODE', then it will return all the attributes
of all nodes in the network. This function allows a front end to pre-load an entire
network's resource attribute information to reduce on function calls.
If type_id is specified, only
If template_id is specified, only
return the resource attributes within the type.
NOTE: This uses discrete queries per resource type for performance reasons.
"""

user_id = kwargs.get('user_id')

net = _get_network(network_id)
net.check_read_permission(user_id, do_raise=True)

resource_attr_qry = db.DBSession.query(ResourceAttr).\
outerjoin(Node, Node.id == ResourceAttr.node_id).\
outerjoin(Link, Link.id == ResourceAttr.link_id).\
outerjoin(ResourceGroup, ResourceGroup.id == ResourceAttr.group_id).filter(
ResourceAttr.ref_key == ref_key,
or_(
and_(ResourceAttr.node_id != None,
ResourceAttr.node_id == Node.id,
Node.network_id == network_id),

and_(ResourceAttr.link_id != None,
ResourceAttr.link_id == Link.id,
Link.network_id == network_id),

and_(ResourceAttr.group_id != None,
ResourceAttr.group_id == ResourceGroup.id,
ResourceGroup.network_id == network_id)
))
resource_attrs = []

if template_id is not None:
attr_ids = []
rs = db.DBSession.query(TypeAttr).join(
TemplateType,
TemplateType.id == TypeAttr.type_id).filter(
TemplateType.template_id == template_id).all()
for r in rs:
attr_ids.append(r.attr_id)

resource_attr_qry = resource_attr_qry.filter(ResourceAttr.attr_id.in_(attr_ids))
ref_key_norm = ref_key.upper()

resource_attrs = resource_attr_qry.all()
# If a template_id is provided, resolve attr_ids via get_template() so that
# inherited type attributes (from parent templates/types) are included.
attr_ids = None
if template_id is not None:
import hydra_base.lib.template as templatelib
template = templatelib.get_template(template_id, **kwargs)
attr_ids = list({ta.attr_id for tt in template.templatetypes for ta in tt.typeattrs})
if not attr_ids:
return []

if ref_key_norm == 'NODE':
qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\
join(Node, Node.id == ResourceAttr.node_id).\
join(Attr, Attr.id == ResourceAttr.attr_id).\
filter(
ResourceAttr.node_id != None,
Node.network_id == network_id,
ResourceAttr.ref_key == ref_key_norm)
if attr_ids is not None:
qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids))
resource_attrs = qry.all()

elif ref_key_norm == 'LINK':
qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\
join(Link, Link.id == ResourceAttr.link_id).\
join(Attr, Attr.id == ResourceAttr.attr_id).\
filter(
ResourceAttr.link_id != None,
Link.network_id == network_id,
ResourceAttr.ref_key == ref_key_norm)
if attr_ids is not None:
qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids))
resource_attrs = qry.all()

elif ref_key_norm == 'GROUP':
qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\
join(ResourceGroup, ResourceGroup.id == ResourceAttr.group_id).\
join(Attr, Attr.id == ResourceAttr.attr_id).\
filter(
ResourceAttr.group_id != None,
ResourceGroup.network_id == network_id,
ResourceAttr.ref_key == ref_key_norm)
if attr_ids is not None:
qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids))
resource_attrs = qry.all()

elif ref_key_norm == 'NETWORK':
qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\
join(Attr, Attr.id == ResourceAttr.attr_id).\
filter(
ResourceAttr.network_id == network_id,
ResourceAttr.ref_key == ref_key_norm)
if attr_ids is not None:
qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids))
resource_attrs = qry.all()

# Convert results to JSONObjects with attribute data included
result_objects = []
for ra in resource_attrs:
ra_obj = JSONObject(ra[0]) # ResourceAttr object
ra_obj.attr_id = ra[2] # Attr.id
ra_obj.name = ra[1] # Attr.name
ra_obj.description = ra[3] # Attr.description
result_objects.append(ra_obj)

return resource_attrs
return result_objects

def get_resource_attributes(ref_key, ref_id, type_id=None, **kwargs):
"""
Expand Down
40 changes: 40 additions & 0 deletions hydra_base/lib/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -2661,6 +2661,46 @@ def get_all_resource_attributes_in_network(attr_id, network_id, include_resource
return json_ra


def get_all_attributes_in_network(network_id, **kwargs):
"""
Find every attribute def (not resource attribute)
Args:
network_id (int): The ID of the network to search
Comment thread
knoxsp marked this conversation as resolved.
Returns:
List of JSONObjects
Raises:
HydraError if the network_id does not exist
"""

user_id = kwargs.get('user_id')

#check the user can read the network
try:
net = db.DBSession.query(Network).filter(Network.id == network_id).one()
except NoResultFound:
raise HydraError("Network %s not found" % (network_id,))
net.check_read_permission(user_id)

network_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).filter(
ResourceAttr.network_id == network_id
)
node_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).join(
Node, ResourceAttr.node_id == Node.id
).filter(Node.network_id == network_id)
link_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).join(
Link, ResourceAttr.link_id == Link.id
).filter(Link.network_id == network_id)
group_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).join(
ResourceGroup, ResourceAttr.group_id == ResourceGroup.id
).filter(ResourceGroup.network_id == network_id)

all_attr_ids = network_attr_ids.union(node_attr_ids, link_attr_ids, group_attr_ids).subquery()

attrs = db.DBSession.query(Attr).join(all_attr_ids, Attr.id == all_attr_ids.c.attr_id).all()

return [JSONObject(a) for a in attrs]


def get_all_resource_data(
scenario_id,
include_metadata=False,
Expand Down
20 changes: 10 additions & 10 deletions hydra_base/lib/template/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,19 +617,19 @@ def update_template(template, auto_delete=False, **kwargs):
should be deleted automatically. This flag is also
used when updating the typeattrs of type. Defaults to False.
"""
tmpl = db.DBSession.query(Template).filter(Template.id == template.id).one()
tmpl.name = template.name
tmpl_i = db.DBSession.query(Template).filter(Template.id == template.id).one()
tmpl_i.name = template.name

if template.status is not None:
tmpl.status = template.status
tmpl_i.status = template.status

if template.description:
tmpl.description = template.description
tmpl_i.description = template.description

template_types = tmpl.get_types()
template_types = tmpl_i.get_types()

if template.layout:
tmpl.layout = get_json_as_string(template.layout)
tmpl_i.layout = get_json_as_string(template.layout)

type_dict = dict([(t.id, t) for t in template_types])

Expand All @@ -640,9 +640,9 @@ def update_template(template, auto_delete=False, **kwargs):
types = template.types if template.types is not None else template.templatetypes
for templatetype in types:

if templatetype.id is not None and templatetype.template_id != tmpl.id:
if templatetype.id is not None and templatetype.template_id != tmpl_i.id:
log.debug("Type %s is a part of a parent template. Ignoring.", templatetype.id)
req_templatetype_ids.append(type_i.id)
req_templatetype_ids.append(templatetype.id)
continue

if templatetype.id is not None:
Expand All @@ -662,9 +662,9 @@ def update_template(template, auto_delete=False, **kwargs):

db.DBSession.flush()

updated_templatetypes = tmpl.get_types()
updated_templatetypes = tmpl_i.get_types()

tmpl_j = JSONObject(tmpl)
tmpl_j = JSONObject(tmpl_i)

tmpl_j.templatetypes = updated_templatetypes

Expand Down
63 changes: 63 additions & 0 deletions tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,69 @@ def test_get_all_resource_attributes_in_network(self, client, network_with_data)
#Find the attribute that ALL nodes have.
assert len(all_network_resource_attrs) == len(network_with_data.nodes)

def test_get_all_attributes_in_network(self, client, network_with_data):
"""
Test that get_all_attributes_in_network returns unique attribute
definitions (not resource attributes) for all resource types in the
network: network-level, nodes, links, and groups.
"""
net = network_with_data

all_attrs = client.get_all_attributes_in_network(net.id)

assert len(all_attrs) > 0

# Collect all attr_ids actually used across every resource type in the network
expected_attr_ids = set()
for ra in net.attributes:
expected_attr_ids.add(ra.attr_id)
for node in net.nodes:
for ra in node.attributes:
expected_attr_ids.add(ra.attr_id)
for link in net.links:
for ra in link.attributes:
expected_attr_ids.add(ra.attr_id)
for group in net.resourcegroups:
for ra in group.attributes:
expected_attr_ids.add(ra.attr_id)

returned_attr_ids = {a.id for a in all_attrs}

# Every attribute used in the network should be in the result
assert expected_attr_ids == returned_attr_ids

def test_get_all_attributes_in_network_deduplication(self, client, network_with_data):
"""
Test that get_all_attributes_in_network de-duplicates attribute
definitions that appear on multiple resources (e.g. the same attr_id
shared by all nodes or shared between links and groups).
"""
net = network_with_data

all_attrs = client.get_all_attributes_in_network(net.id)

# Each attribute definition must appear exactly once
returned_ids = [a.id for a in all_attrs]
assert len(returned_ids) == len(set(returned_ids)), (
"Duplicate attribute IDs found in get_all_attributes_in_network result"
)

def test_get_all_attributes_in_network_permissions(self, client, projectmaker, networkmaker):
"""
Test that a user without read permission on the network cannot call
get_all_attributes_in_network.
"""
# Create a project that is NOT shared with other users
private_proj = projectmaker.create(name=None, share=False)
net = networkmaker.create(project_id=private_proj.id)

# UserD has not been granted access to this private network/project
client.login('UserD', 'password')
try:
with pytest.raises(hb.exceptions.HydraError):
client.get_all_attributes_in_network(net.id)
finally:
client.login('root', '')

def test_get_network_1(self, client, networkmaker):
"""
Expand Down