Skip to content

Commit e4a7802

Browse files
d-linkoDavidEdellDavid Linko
authored
187 improve initial default adm load process (#323)
* Grafana updates including: Upgraded to v12, switched internal DB to PostgresSQL, consolidated dashboard+widget definitions and moved to a text provisioning file, and updated anms-ui monitor to use the new dashboard. Address #282 and #283 * Added to UI a prototype Help page and a Not Found page handler. * Resolve default database warning in grafana (hopefully). * Added upgrading file * Add amp-manager to UI Status page * added inifinity-datasource plugin and example panels * updated layout of panels * Fix core '/services' REST API to return JSON instead of text * Core REST API bugfixes to detect timeouts and resolve potential error with overloaded status variable. * initial dev creating a internal transcoder * working version, added new profile default is internal transcoding * working transcoder fixed issue with threading and DB * Automatically create GHCR images when a tag is created. This supplements the formal release process which also triggers publication. * Fix ghcr name if triggered by tag * Fixing grafana authnz/demo websocket proxy configuration. * removed services related to transcoder since they arent up by default * added --ignore flag for podman volume creaete to avoid name error * moved grafana db password to enviroment var * moving adms init * moved init adms logic into transcoder * added new folder for adding new ADMS in core * added gitignore to extra_adms to avoid extra adms be added to main * moved init logic to avoid loop startup error * loading default ADMs controlled by a new route * removed uneeded f-string --------- Co-authored-by: David Edell <david.edell@jhuapl.edu> Co-authored-by: David Linko <david.linko@jhuapl.edu>
1 parent 3d10915 commit e4a7802

File tree

13 files changed

+151
-159
lines changed

13 files changed

+151
-159
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ point. With ANMS running, go to `localhost:8080` and log in to the database with
193193

194194
### ADM and Agent Updates
195195

196-
Changes to ADMs are handled on the Manager by uploading a new version of the ADM via the Web UI.
196+
By default after building the system, ANMS starts with the ADMs defined in `deps/dtnma-adms` and ADMs added to `anms-core/extra_adms`. Changes to and adding new ADMs are handled on the Manager by uploading a new version of the ADM via the Web UI or the REST POST endpoint `http://localhost:5555/adms/`.
197197
The manager will then be able to use the new ADM.
198198

199199
Changes to a test Agent are more complicated, and require auto-generated C sources built into the ION source tree.

anms-core/anms/init_adms.py

Lines changed: 0 additions & 81 deletions
This file was deleted.

anms-core/anms/routes/ARIs/ari.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
# subcontract 1658085.
2323
#
2424
import asyncio
25-
from functools import cache
2625
from typing import List
2726

2827
from fastapi import Depends, APIRouter
@@ -36,7 +35,6 @@
3635
from anms.models.relational import get_async_session
3736
from anms.models.relational.actual_parameter import ActualParameter
3837
from anms.models.relational.ari import ARI
39-
from anms.models.relational.adms.data_model_view import DataModel
4038
from anms.models.relational.formal_parameter import FormalParameter
4139

4240

anms-core/anms/routes/adms/adm.py

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -126,71 +126,11 @@ async def remove_adm(enumeration: int, namespace:str):
126126
logger.debug(f"ADM ENUM:{enumeration} in NAMESPACE {namespace} not a known ADM")
127127
raise HTTPException(status_code = status.HTTP_400_BAD_REQUEST, detail = f"ADM ENUM:{enumeration} in NAMESPACE {namespace} not a known ADM")
128128

129-
130-
131-
async def handle_adm(admset: ace.AdmSet, adm_file: ace.models.AdmModule, session, replace=True):
132-
''' Process a received and decoded ADM into the ANMS DB.
133-
134-
:param replace: If true and the ADM exists it will be checked and replaced.
135-
:return: A list of issues with the ADM, which is empty if successful.
136-
'''
137-
logger.info("Adm name: %s", adm_file.norm_name)
138-
data_model_view = await DataModel.get(adm_file.ns_model_enum,adm_file.ns_org_name )
139-
if data_model_view:
140-
if not replace:
141-
logger.info('Not replacing existing ADM name %s', adm_file.norm_name)
142-
return []
143-
data_rec = None
144-
async with get_async_session() as session:
145-
data_rec,_ = await AdmData.get(data_model_view.data_model_id,session)
146-
147-
if data_rec:
148-
# Compare old and new contents
149-
logger.info("Checking existing ADM name %s", adm_file.norm_name)
150-
old_adm = admset.load_from_data(io.BytesIO(data_rec.data), del_dupe=False)
151-
comp = AdmCompare(admset)
152-
if not comp.compare_adms(old_adm, adm_file):
153-
issues = comp.get_errors()
154-
else:
155-
issues = [f"Updating existing adm is not allowed yet"]
156-
return issues
157-
158-
logger.info("Inserting ADM name %s", adm_file.norm_name)
159-
160-
# Use CAmPython to generate sql
161-
out_path = "" # This is empty string since we don't need to write the generated sql to a file
162-
sql_dialect = 'pgsql'
163-
writer = create_sql.Writer(admset, adm_file, out_path, sql_dialect)
164-
string_buffer = io.StringIO()
165-
writer.write(string_buffer)
166-
167-
# execute generated Sql
168-
queries = string_buffer.getvalue()
169-
try:
170-
await session.execute(queries)
171-
await session.commit()
172-
except Exception as err:
173-
logger.error(f"{sql_dialect} execution error: {err.args}")
174-
logger.debug('%s', traceback.format_exc())
175-
raise
176-
177-
# Save the adm file of the new adm
178-
179-
180-
buf = io.StringIO()
181-
ace.adm_yang.Encoder().encode(adm_file, buf)
182-
ret_dm = await DataModel.get(adm_file.ns_model_enum, adm_file.ns_org_name, session)
183-
184-
# Write the encoded string data to the BytesIO object
185-
bytes_io = io.BytesIO()
186-
bytes_io.write(buf.getvalue().encode('utf-8'))
187-
# Reset the pointer to the beginning
188-
bytes_io.seek(0)
189-
data = {"enumeration":ret_dm.data_model_id, "data": bytes_io.getvalue()}
190-
await AdmData.add_data(data, session)
191-
192-
return []
193-
129+
@router.post("/load_default", status_code=status.HTTP_201_CREATED)
130+
async def load_default_adm():
131+
await TRANSMORGIFIER.load_default_adms()
132+
response = JSONResponse(status_code=status.HTTP_200_OK, content={"message": "Initilized default ADMs", "error_details": ""})
133+
return response
194134

195135
@router.post("/", status_code=status.HTTP_201_CREATED,
196136
responses={400: {"model": RequestError}, 405: {"model": UpdateAdmError}, 500: {"model": RequestError}})

anms-core/anms/shared/transmogrifier.py

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@
2121
# the prime contract 80NM0018D0004 between the Caltech and NASA under
2222
# subcontract 1658085.
2323

24-
24+
from camp.generators import (create_sql)
2525
from anms.shared.config import ConfigBuilder
26-
from anms.shared.logger import Logger
26+
import asyncio
2727
import anms.shared.mqtt_client
2828
from anms.shared.opensearch_logger import OpenSearchLogger
2929
from anms.models.relational import get_session
30+
from anms.models.relational import get_async_session
3031
from anms.models.relational.transcoder_log import TranscoderLog
32+
from anms.models.relational.adms import (adm_data, data_model_view)
33+
from anms.routes.adms.adm_compare import (AdmCompare)
34+
3135
import traceback
3236
import ace
3337
import io
@@ -40,19 +44,20 @@
4044

4145

4246
# depending on what the config is for core will either use a MQTT server to send off commands or
43-
# use an internal
47+
# use an ACE internally to translate
4448
class Transmorgifier:
4549

4650
''' The Transmogifier that can be configured to use an external or internal translator. '''
4751
# args = config
4852
def __init__(self, args):
4953
# if the transcoding in internal to core
5054
LOGGER.info(config.Transcoder)
55+
self.adm_data = adm_data.AdmData
56+
self.data_model = data_model_view.DataModel
5157
if config.Transcoder == "Internal":
5258
db_uri = f"postgresql://{config.DB_USER}:{config.DB_PASS}@{config.DB_HOST}/{config.DB_CHROOT}"
5359
LOGGER.info(f'Connecting to SQL DB at {db_uri}')
5460
self._dbeng = sqlalchemy.create_engine(db_uri)
55-
5661
self.transcode = self._transcode_internal
5762
self.reload = self._reload_internal
5863
self._adm_reload(None)
@@ -61,6 +66,87 @@ def __init__(self, args):
6166
self.MQTT_CLIENT = anms.shared.mqtt_client.MQTT_CLIENT
6267
self.transcode = self._transcode_mqtt
6368
self.reload = self._reload_mqtt
69+
70+
async def handle_adm(self, admset: ace.AdmSet, adm_file: ace.models.AdmModule, session, replace=True):
71+
''' Process a received and decoded ADM into the ANMS DB.
72+
73+
:param replace: If true and the ADM exists it will be checked and replaced.
74+
:return: A list of issues with the ADM, which is empty if successful.
75+
'''
76+
LOGGER.info("Adm name: %s", adm_file.norm_name)
77+
data_model_view = await self.data_model.get(adm_file.ns_model_enum,adm_file.ns_org_name )
78+
if data_model_view:
79+
if not replace:
80+
LOGGER.info('Not replacing existing ADM name %s', adm_file.norm_name)
81+
return []
82+
data_rec = None
83+
async with get_async_session() as session:
84+
data_rec,_ = await self.adm_data.get(data_model_view.data_model_id,session)
85+
86+
if data_rec:
87+
# Compare old and new contents
88+
LOGGER.info("Checking existing ADM name %s", adm_file.norm_name)
89+
old_adm = admset.load_from_data(io.BytesIO(data_rec.data), del_dupe=False)
90+
comp = AdmCompare(admset)
91+
if not comp.compare_adms(old_adm, adm_file):
92+
issues = comp.get_errors()
93+
else:
94+
issues = ["Updating existing adm is not allowed yet"]
95+
return issues
96+
97+
LOGGER.info("Inserting ADM name %s", adm_file.norm_name)
98+
99+
# Use CAmPython to generate sql
100+
out_path = "" # This is empty string since we don't need to write the generated sql to a file
101+
sql_dialect = 'pgsql'
102+
writer = create_sql.Writer(admset, adm_file, out_path, sql_dialect)
103+
string_buffer = io.StringIO()
104+
writer.write(string_buffer)
105+
106+
# execute generated Sql
107+
queries = string_buffer.getvalue()
108+
try:
109+
await session.execute(queries)
110+
await session.commit()
111+
except Exception as err:
112+
LOGGER.error(f"{sql_dialect} execution error: {err.args}")
113+
LOGGER.debug('%s', traceback.format_exc())
114+
raise
115+
116+
# Save the adm file of the new adm
117+
buf = io.StringIO()
118+
ace.adm_yang.Encoder().encode(adm_file, buf)
119+
ret_dm = await self.data_model.get(adm_file.ns_model_enum, adm_file.ns_org_name, session)
120+
121+
# Write the encoded string data to the BytesIO object
122+
bytes_io = io.BytesIO()
123+
bytes_io.write(buf.getvalue().encode('utf-8'))
124+
# Reset the pointer to the beginning
125+
bytes_io.seek(0)
126+
data = {"enumeration":ret_dm.data_model_id, "data": bytes_io.getvalue()}
127+
await self.adm_data.add_data(data, session)
128+
129+
return []
130+
131+
async def load_default_adms(self):
132+
admset = ace.AdmSet(cache_dir=False)
133+
admset.load_default_dirs()
134+
issues = ace.Checker(admset.db_session()).check()
135+
for iss in issues:
136+
LOGGER.error('ADM issue %s', iss)
137+
138+
for adm_file in admset:
139+
try:
140+
LOGGER.info('ADM %s handling started', adm_file.norm_name)
141+
async with get_async_session() as db_sess:
142+
await self.handle_adm(admset, adm_file, db_sess, replace=False)
143+
LOGGER.info('ADM %s handling finished', adm_file.norm_name)
144+
except Exception as err:
145+
# The function already logged any SQL issue at error severity
146+
LOGGER.error('ADM %s handling failed: %s', adm_file.norm_name, err)
147+
LOGGER.debug('%s', traceback.format_exc())
148+
149+
self.reload()
64150

65151
def _transcode_mqtt(self, input):
66152
msg = json.dumps({'uri': input})
@@ -101,7 +187,6 @@ def _ace_transcode(self, input):
101187
dec = ace.ari_cbor.Decoder()
102188
ari = dec.decode(io.BytesIO(in_bytes))
103189
LOGGER.debug(f'decoded as ARI {ari}')
104-
# ace.nickname.Converter(ace.nickname.Mode.FROM_NN, admsSession(self._dbeng), True)(ari)
105190
ari = ace.nickname.Converter(ace.nickname.Mode.FROM_NN, adms.db_session(), False)(ari)
106191

107192
except Exception as err:

anms-core/docker-entrypoint.sh

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,4 @@ set -e
2727

2828
PYTHON=${PYTHON:-python3}
2929

30-
# initialize DB state
31-
${PYTHON} -m anms.init_adms
32-
3330
exec ${PYTHON} -m anms.run_gunicorn

anms-core/extra_adms/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#ignoring any added ADMs to this file to avoid uploading test or unwanted ADMs
2+
*
3+
!.gitignore

anms-ui/public/app/components/adm/Adm.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,16 @@ export default {
108108
},
109109
async mounted() {
110110
if (!this.hasAdms) {
111+
await this.loadAdms();
111112
await this.getAdms();
112113
}
113114
},
114115
methods: {
115116
...mapActions("adm", {
116117
getAdms: "getAdms",
117-
uploadAdm: "uploadAdm"
118+
uploadAdm: "uploadAdm",
119+
loadAdms: "loadAdms"
120+
118121
}),
119122
download(adm){
120123
let json = {};

anms-ui/public/app/shared/api_adm.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,13 @@ const apiUpdateAdm = async (file) => {
5353
);
5454
};
5555

56+
const apiLoadAdm = async () => {
57+
return axios.post(adm_url+"/load_default", {headers: {accept: 'application/json'}})
58+
};
59+
5660
export default {
5761
apiGetAdms,
5862
apiGetAdm,
59-
apiUpdateAdm
63+
apiUpdateAdm,
64+
apiLoadAdm
6065
};

anms-ui/public/app/store/modules/adm.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ export default {
7272

7373
});
7474
},
75+
async loadAdms({ state, commit }){
76+
commit('loading', true);
77+
commit('requestError', "");
78+
let sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));
79+
api_adm.apiLoadAdm()
80+
.catch(function (error) {
81+
sleep(1000).then(() => {
82+
commit('adms', []);
83+
commit('requestError', error);
84+
commit('loading', false);
85+
})
86+
});
87+
},
7588
async uploadAdm({ state, commit }, adm_file){
7689
//commit('loading', true);
7790
commit('requestError', "");

0 commit comments

Comments
 (0)