Skip to content

Commit b25a85e

Browse files
committed
System creator refactoring
1 parent 762eea8 commit b25a85e

2 files changed

Lines changed: 128 additions & 80 deletions

File tree

enginecore/enginecore/model/system_modeler.py

Lines changed: 126 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,97 @@
1313

1414
graph_ref = GraphReference()
1515

16-
def to_camelcase(s):
16+
def _get_props_stm(attr, supported_attr=[]):
17+
"""Format dict attributes as neo4j props"""
18+
19+
existing = dict(
20+
filter(lambda k: attr[k[0]] != None and (not supported_attr or k[0] in supported_attr), attr.items())
21+
)
22+
return ','.join(map(lambda k: "{}: {}".format(_to_camelcase(k), repr(existing[k])), existing))
23+
24+
25+
def _get_set_stm(attr, node_name="asset", supported_attr=[]):
26+
"""Format dict as neo4j set statement"""
27+
28+
existing = dict(
29+
filter(lambda k: attr[k[0]] != None and (not supported_attr or k[0] in supported_attr), attr.items())
30+
)
31+
return ','.join(map(lambda k: "{}.{}={}".format(node_name, _to_camelcase(k), repr(existing[k])), existing))
32+
33+
34+
def _add_psu(key, psu_index, attr):
35+
"""Add a PSU to an existing server
36+
37+
Args:
38+
key(int): server key psu will belong to
39+
psu_index(int): psu number
40+
attr(dict): psu attributes such as power_source, power_consumption, variation & draw
41+
"""
42+
43+
with graph_ref.get_session() as session:
44+
45+
query = []
46+
# find the server
47+
query.append("MATCH (asset:Asset {{ key: {} }})".format(key))
48+
49+
# create a PSU
50+
attr['key'] = int("{}{}".format(key, psu_index))
51+
attr['name'] = 'psu' + str(psu_index)
52+
attr['type'] = 'psu'
53+
54+
props_stm = _get_props_stm(attr)
55+
query.append("CREATE (psu:Asset:PSU:Component {{ {} }})".format(props_stm))
56+
57+
# set relationships
58+
query.append("CREATE (asset)-[:HAS_COMPONENT]->(psu)")
59+
query.append("CREATE (asset)-[:POWERED_BY]->(psu)")
60+
61+
session.run("\n".join(query))
62+
63+
64+
def _to_camelcase(s):
1765
return re.sub(r'(?!^)_([a-zA-Z])', lambda m: m.group(1).upper(), s)
1866

67+
68+
def _generate_id(size=12, chars=string.ascii_uppercase + string.digits):
69+
""" Ref: https://stackoverflow.com/a/23728630"""
70+
return ''.join(secrets.choice(chars) for _ in range(size))
71+
72+
73+
def _generate_mac():
74+
return ''.join(random.choice('0123456789abcdef') for _ in range(12))
75+
76+
1977
def configure_asset(key, attr):
20-
"""Update existing properties"""
78+
"""Update existing properties
79+
80+
Args:
81+
key(int): key of the asset to be configured
82+
attr(dict): asset props' updates
83+
"""
84+
2185
if 'func' in attr and attr['func']:
2286
del attr['func']
2387

2488
if 'asset_key' in attr and attr['asset_key']:
2589
del attr['asset_key']
2690

2791
with graph_ref.get_session() as session:
28-
29-
existing = dict(filter(lambda k: attr[k[0]] != None, attr.items()))
30-
# print(existing)
31-
# existing = filter(lambda k: "asset.{}={}".format(k, repr(attr[k])) if attr[k] else None, attr)
32-
set_statement = ','.join(map(lambda k: "asset.{}={}".format(to_camelcase(k), repr(existing[k])), existing))
92+
93+
set_statement = _get_set_stm(attr)
3394
query = "MATCH (asset:Asset {{ key: {key} }}) SET {set_stm}".format(key=key, set_stm=set_statement)
3495

3596
session.run(query)
3697

98+
3799
def remove_link(source_key, dest_key):
38-
"""Remove existing power connection"""
100+
"""Remove existing power connection
101+
102+
Args:
103+
source_key(int): key of the parent asset
104+
dest_key(int): key of the asset powered by source_key
105+
"""
106+
39107
with graph_ref.get_session() as session:
40108
result = session.run("""
41109
MATCH (src:Asset {key: $src_key})<-[power_link:POWERED_BY]-(dst:Asset {key: $dest_key})
@@ -44,7 +112,13 @@ def remove_link(source_key, dest_key):
44112

45113

46114
def link_assets(source_key, dest_key):
47-
"""Power a component by another component """
115+
"""Power a component by another component
116+
117+
Args:
118+
source_key(int): key of the parent asset
119+
dest_key(int): key of the asset to be powered by the parent
120+
"""
121+
48122
with graph_ref.get_session() as session:
49123

50124
# Validate that the asset does not power already existing device
@@ -80,37 +154,17 @@ def link_assets(source_key, dest_key):
80154
if (not record) or (not 'link' in dict(record)):
81155
print('Invalid link configuration was provided')
82156

83-
def _add_psu(key, psu_index, power_consumption, power_source, variation, draw_percentage=1):
84-
with graph_ref.get_session() as session:
85-
session.run("\
86-
MATCH (asset:Asset {key: $pkey})\
87-
CREATE (psu:Asset:PSU:Component { \
88-
name: $psuname,\
89-
key: $psukey,\
90-
draw: $draw,\
91-
type: 'psu',\
92-
powerConsumption: $pc,\
93-
powerSource: $ps,\
94-
variation: $var \
95-
})\
96-
CREATE (asset)-[:HAS_COMPONENT]->(psu)\
97-
CREATE (asset)-[:POWERED_BY]->(psu)",
98-
pkey=key,
99-
pc=power_consumption,
100-
ps=power_source,
101-
psuname='psu'+psu_index,
102-
draw=draw_percentage,
103-
psukey=int("{}{}".format(key, psu_index)),
104-
var=variation.name.lower()
105-
)
106-
107157

108158
def create_outlet(key, attr):
109159
"""Add outlet to the model """
160+
110161
with graph_ref.get_session() as session:
111-
session.run("\
112-
CREATE (:Asset:Outlet { name: $name, key: $key, type: 'outlet' })", key=key, name="out-{}".format(key))
113-
set_properties(key, attr)
162+
attr['name'] = attr['name'] if 'name' in attr and attr['name'] else "out-{}".format(key)
163+
attr['type'] = 'outlet'
164+
attr['key'] = key
165+
166+
props_stm = _get_props_stm(attr, supported_attr=["name", "type", "key", "off_delay", "on_delay"])
167+
session.run("CREATE (:Asset:Outlet {{ {} }})".format(props_stm))
114168

115169

116170
class ServerVariations(Enum):
@@ -135,6 +189,7 @@ def create_server(key, attr, server_variation=ServerVariations.Server):
135189
if not attr['domain_name']:
136190
raise KeyError('Must provide VM name (domain name)')
137191

192+
# Validate server domain name
138193
try:
139194
conn = libvirt.open("qemu:///system")
140195
conn.lookupByName(attr['domain_name'])
@@ -146,35 +201,36 @@ def create_server(key, attr, server_variation=ServerVariations.Server):
146201

147202
with graph_ref.get_session() as session:
148203

149-
session.run("\
150-
CREATE (server:Asset { name: $name, domainName: $name, key: $key, type: $stype }) SET server :"+server_variation.name,
151-
key=key, name=attr['domain_name'], stype=server_variation.name.lower())
204+
query = [] # cypher query
152205

206+
attr['name'] = attr['name'] if 'name' in attr and attr['name'] else attr['domain_name']
207+
attr['type'] = server_variation.name.lower()
208+
attr['key'] = key
209+
210+
s_attr = ["name", "domain_name", "type", "key", "off_delay", "on_delay", "power_consumption", "power_source"]
211+
props_stm = _get_props_stm(attr, supported_attr=s_attr)
212+
213+
# create a server
214+
query.append("CREATE (server:Asset {{ {} }}) SET server :{}".format(props_stm, server_variation.name))
215+
216+
# set BMC-server specific attributes if type is bmc
153217
if server_variation == ServerVariations.ServerWithBMC:
154-
bmc_attr = {**IPMI_LAN_DEFAULTS, **attr}
155-
session.run("""
156-
MATCH (a:Asset {key: $key})
157-
SET a.user=$user, a.password=$password, a.host=$host, a.port=$port, a.vmport=$vmport
158-
""",
159-
key=key,
160-
user=bmc_attr['user'],
161-
password=bmc_attr['password'],
162-
host=bmc_attr['host'],
163-
port=bmc_attr['port'],
164-
vmport=bmc_attr['vmport']
165-
)
218+
bmc_attr = {**IPMI_LAN_DEFAULTS, **attr} # merge
166219

220+
set_stm = _get_set_stm(bmc_attr, node_name="server", supported_attr=IPMI_LAN_DEFAULTS.keys())
221+
query.append("SET {}".format(set_stm))
167222

168-
set_properties(key, attr)
223+
session.run("\n".join(query))
224+
225+
# add PSUs to the model
169226
for i in range(attr['psu_num']):
170-
_add_psu(
171-
key,
172-
str(i+1),
173-
attr['psu_power_consumption'],
174-
attr['psu_power_source'],
175-
server_variation,
176-
attr['psu_load'][i] if attr['psu_load'] else 1
177-
)
227+
psu_attr = {
228+
"power_consumption": attr['psu_power_consumption'],
229+
"power_source": attr['psu_power_source'],
230+
"variation": server_variation.name.lower(),
231+
"draw": attr['psu_load'][i] if attr['psu_load'] else 1
232+
}
233+
_add_psu(key, psu_index=i+1, attr=psu_attr)
178234

179235

180236
def set_properties(key, attr):
@@ -210,14 +266,6 @@ def set_properties(key, attr):
210266
SET asset.imgUrl=$img_url", pkey=key, img_url=attr['img_url'])
211267

212268

213-
def id_generator(size=12, chars=string.ascii_uppercase + string.digits):
214-
""" Ref: https://stackoverflow.com/a/23728630"""
215-
return ''.join(secrets.choice(chars) for _ in range(size))
216-
217-
218-
def mac_generator():
219-
return ''.join(random.choice('0123456789abcdef') for _ in range(12))
220-
221269

222270
def create_ups(key, attr, preset_file=os.path.join(os.path.dirname(__file__), 'presets/apc_ups.json')):
223271
"""Add UPS to the system model """
@@ -267,10 +315,10 @@ def create_ups(key, attr, preset_file=os.path.join(os.path.dirname(__file__), 'p
267315

268316
for k, v in data["OIDs"].items():
269317
if k == 'SerialNumber':
270-
v['defaultValue'] = id_generator()
318+
v['defaultValue'] = _generate_id()
271319

272320
if k == 'MAC':
273-
v['defaultValue'] = mac_generator()
321+
v['defaultValue'] = _generate_mac()
274322

275323
if k == "BasicBatteryStatus":
276324
oid_desc = dict((y,x) for x,y in v["oidDesc"].items())
@@ -467,10 +515,10 @@ def create_pdu(key, attr, preset_file=os.path.join(os.path.dirname(__file__), 'p
467515
# Add PDU OIDS to the model
468516
for k, v in data["OIDs"].items():
469517
if k == 'SerialNumber':
470-
v['defaultValue'] = id_generator()
518+
v['defaultValue'] = _generate_id()
471519

472520
if k == 'MAC':
473-
v['defaultValue'] = mac_generator()
521+
v['defaultValue'] = _generate_mac()
474522

475523
session.run("\
476524
MATCH (pdu:PDU {key: $pkey})\
@@ -595,10 +643,10 @@ def drop_model():
595643
def delete_asset(key):
596644
""" Delete by key """
597645
with graph_ref.get_session() as session:
598-
session.run("MATCH (a:Asset { key: $key }) \
599-
OPTIONAL MATCH (a)-[:HAS_COMPONENT]->(s) \
600-
OPTIONAL MATCH (a)-[:HAS_OID]->(oid) \
601-
OPTIONAL MATCH (a)-[:HAS_BATTERY]->(b) \
602-
OPTIONAL MATCH (oid)-[:HAS_STATE_DETAILS]->(sd) \
603-
DETACH DELETE a,s,oid,sd,b", key=key)
646+
session.run("""MATCH (a:Asset { key: $key })
647+
OPTIONAL MATCH (a)-[:HAS_COMPONENT]->(s)
648+
OPTIONAL MATCH (a)-[:HAS_OID]->(oid)
649+
OPTIONAL MATCH (a)-[:HAS_BATTERY]->(b)
650+
OPTIONAL MATCH (oid)-[:HAS_STATE_DETAILS]->(sd)
651+
DETACH DELETE a,s,oid,sd,b""", key=key)
604652

enginecore/simengine-cli

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ create_asset_parent = argparse.ArgumentParser(add_help=False)
250250
create_asset_parent.add_argument(
251251
'-k', '--asset-key', type=int, required=True, help="Unique asset key (must be <= 9999)"
252252
)
253-
create_asset_parent.add_argument('--on-delay', type=int, help="Power on delay in ms", default=-1)
254-
create_asset_parent.add_argument('--off-delay', type=int, help="Power on delay in ms", default=-1)
253+
create_asset_parent.add_argument('--on-delay', type=int, help="Power on delay in ms", default=0)
254+
create_asset_parent.add_argument('--off-delay', type=int, help="Power on delay in ms", default=0)
255255
create_asset_parent.add_argument('-n', '--name', help="Name displayed on the UI")
256256

257257
# snmp group parent will contain snmp-specific args

0 commit comments

Comments
 (0)