1212import socket
1313import errno
1414from collections import deque
15- from .util import mw , mw_logger
15+ from .util import mw , mw_logger , validators
1616from .util .exceptions import (
1717 LicensingError ,
1818 InternalError ,
1919 OnlineLicensingError ,
2020 EntitlementError ,
2121 MatlabInstallError ,
22+ NetworkLicensingError ,
2223 log_error ,
2324)
2425
26+
2527logger = mw_logger .get ()
2628
2729
@@ -42,21 +44,59 @@ def __init__(self, settings):
4244 logger .error ("'matlab' executable not found in PATH" )
4345 return
4446
47+ def __get_cached_licensing_file (self ):
48+ """Get the cached licensing file
49+
50+ Returns:
51+ Path : Path object to cached licensing file
52+ """
53+ return self .settings ["matlab_config_file" ]
54+
55+ def __delete_cached_licensing_file (self ):
56+ try :
57+ logger .info (f"Deleting any cached licensing files!" )
58+ os .remove (self .__get_cached_licensing_file ())
59+ except FileNotFoundError :
60+ # The file being absent is acceptable.
61+ pass
62+
63+
64+ def __reset_and_delete_cached_licensing (self ):
65+ logger .info (f"Resetting cached licensing information..." )
66+ self .licensing = None
67+ self .__delete_cached_licensing_file ()
68+
4569 async def init_licensing (self ):
46- """Initialise licensing from persisted details or environment variable."""
70+ """Initialize licensing from environment variable or cached file.
71+
72+ Greater precedence is given to value specified in environment variable MLM_LICENSE_FILE
73+ If specified, this function will delete previously cached licensing information.
74+ This enforces a clear understanding of what was used to initialize licensing.
75+ The contents of the environment variable are NEVER cached.
76+ """
4777
48- # Persisted licensing details present
49- if self .settings ["matlab_config_file" ].exists ():
50- with open (self .settings ["matlab_config_file" ], "r" ) as f :
51- config = json .loads (f .read ())
78+ # Default value
79+ self .licensing = None
5280
53- if "licensing" in config :
54- # TODO Refactoring of config file reading/writing
55- licensing = config ["licensing" ]
81+ # NLM Connection String set in environment
82+ if self .settings ["nlm_conn_str" ] is not None :
83+ nlm_licensing_str = self .settings ["nlm_conn_str" ]
84+ logger .info (f"Found NLM:[{ nlm_licensing_str } ] set in environment" )
85+ logger .info (f"Using NLM string to connect ... " )
86+ self .licensing = {
87+ "type" : "nlm" ,
88+ "conn_str" : nlm_licensing_str ,
89+ }
90+ self .__delete_cached_licensing_file ()
5691
57- # If there is any problem loading config, remove it and persist
92+ # If NLM connection string is not present, then look for persistent LNU info
93+ elif self .__get_cached_licensing_file ().exists ():
94+ with open (self .__get_cached_licensing_file (), "r" ) as f :
95+ licensing = json .loads (f .read ())
96+ logger .info ("Found cached licensing information..." )
5897 try :
5998 if licensing ["type" ] == "nlm" :
99+ # Note: Only NLM settings entered in browser were cached.
60100 self .licensing = {
61101 "type" : "nlm" ,
62102 "conn_str" : licensing ["conn_str" ],
@@ -82,26 +122,19 @@ async def init_licensing(self):
82122 ) - timedelta (hours = 1 )
83123
84124 if expiry_window > datetime .now (timezone .utc ):
85- await self .update_entitlements ()
125+ successful_update = await self .update_entitlements ()
126+ if successful_update :
127+ logger .info ("Successful re-use of cached information." )
128+ self .persist_licensing ()
129+ else :
130+ self .__reset_and_delete_cached_licensing ()
86131 else :
87- # Reset licensing and persist
88- self .licensing ["identity_token" ] = None
89- self .licensing ["source_id" ] = None
90- self .licensing ["expiry" ] = None
91- self .licensing ["entitlements" ] = []
92- self .persist_licensing ()
132+ self .__reset_and_delete_cached_licensing ()
133+ else :
134+ # Somethings wrong, licensing is neither NLM or MHLM
135+ self .__reset_and_delete_cached_licensing ()
93136 except Exception as e :
94- logger .error ("Error parsing config, resetting." )
95- self .licensing = None
96- self .persist_licensing ()
97-
98- # NLM Connection String set in environment
99- # TODO Validate connection string
100- elif self .settings ["nlm_conn_str" ] is not None :
101- self .licensing = {
102- "type" : "nlm" ,
103- "conn_str" : self .settings ["nlm_conn_str" ],
104- }
137+ self .__reset_and_delete_cached_licensing ()
105138
106139 def get_matlab_state (self ):
107140 """Determine the state of MATLAB to be down/starting/up."""
@@ -208,6 +241,12 @@ def is_matlab_present(self):
208241 return self .settings ["matlab_path" ] is not None
209242
210243 async def update_entitlements (self ):
244+ """Speaks to MW and updates MHLM entitlements
245+
246+ Returns: True if update was successful
247+ Raises:
248+ InternalError: OnlineLicensingError, EntitlementError
249+ """
211250 if self .licensing is None or self .licensing ["type" ] != "mhlm" :
212251 raise InternalError (
213252 "MHLM licensing must be configured to update entitlements!"
@@ -230,7 +269,7 @@ async def update_entitlements(self):
230269 except OnlineLicensingError as e :
231270 self .error = e
232271 log_error (logger , e )
233- return
272+ return False
234273 except EntitlementError as e :
235274 self .error = e
236275 log_error (logger , e )
@@ -244,48 +283,28 @@ async def update_entitlements(self):
244283 self .licensing ["profile_id" ] = None
245284 self .licensing ["entitlements" ] = []
246285 self .licensing ["entitlement_id" ] = None
247- return
286+ return False
248287
249288 self .licensing ["entitlements" ] = entitlements
250289
251290 # If there is only one non-expired entitlement, set it as active
252291 # TODO Also, for now, set the first entitlement as active if there are multiple
253292 self .licensing ["entitlement_id" ] = entitlements [0 ]["id" ]
254293
255- def persist_licensing (self ):
256- config_file = self .settings ["matlab_config_file" ]
257- if config_file .exists ():
258- with open (config_file , "r" ) as f :
259- config = json .loads (f .read ())
260- else :
261- config = {}
294+ # Successful update
295+ return True
262296
297+ def persist_licensing (self ):
298+ """Saves licensing information to file"""
263299 if self .licensing is None :
264- if "licensing" in config :
265- del config ["licensing" ]
266- elif self .licensing ["type" ] == "mhlm" :
267- config ["licensing" ] = {
268- "type" : "mhlm" ,
269- "identity_token" : self .licensing ["identity_token" ],
270- "source_id" : self .licensing ["source_id" ],
271- "expiry" : self .licensing ["expiry" ],
272- "email_addr" : self .licensing ["email_addr" ],
273- "first_name" : self .licensing ["first_name" ],
274- "last_name" : self .licensing ["last_name" ],
275- "display_name" : self .licensing ["display_name" ],
276- "user_id" : self .licensing ["user_id" ],
277- "profile_id" : self .licensing ["profile_id" ],
278- "entitlement_id" : self .licensing ["entitlement_id" ],
279- }
280- elif self .licensing ["type" ] == "nlm" :
281- config ["licensing" ] = {
282- "type" : "nlm" ,
283- "conn_str" : self .licensing ["conn_str" ],
284- }
285-
286- config_file .parent .mkdir (parents = True , exist_ok = True )
287- with open (config_file , "w" ) as f :
288- f .write (json .dumps (config ))
300+ self .__delete_cached_licensing_file ()
301+
302+ elif self .licensing ["type" ] in ["mhlm" , "nlm" ]:
303+ logger .info ("Saving licensing information..." )
304+ cached_licensing_file = self .__get_cached_licensing_file ()
305+ cached_licensing_file .parent .mkdir (parents = True , exist_ok = True )
306+ with open (cached_licensing_file , "w" ) as f :
307+ f .write (json .dumps (self .licensing ))
289308
290309 def reserve_matlab_port (self ):
291310 """Reserve a free port for MATLAB Embedded Connector in the allowed range."""
0 commit comments