diff --git a/oc/logging.py b/oc/logging.py index 4d36e50..1f0220a 100755 --- a/oc/logging.py +++ b/oc/logging.py @@ -59,18 +59,22 @@ def load_config(path, is_cp_file=False): """ load the config file from default configuration file 'od.config' if is_cp_file is True load the config file PATH if is_cp_file is False """ - cfg = None + cfg_logging = None if is_cp_file is True: - logger.info("Reading cherrypy configuration section 'global/logging': path = %s", path) - cfg = Config(path)['global']['logging'] + logger.info(f"Reading cherrypy configuration section 'global/logging': path = {path}") + config = Config(path) + if isinstance( config.get('global'), dict ): + cfg_logging = config.get('global').get('logging') + else: + cfg_logging = config.get('logging') else: - logger.info("Reading json file: path = %s", path) + logger.info(f"Reading json file: path = {path}") with open(path, encoding='UTF-8') as f: - cfg = json.decode(f.read()) + cfg_logging = json.decode(f.read()) - logger.debug("config = %s", repr(cfg)) - return cfg + logger.debug(f"logging configuration : {cfg_logging}") + return cfg_logging def init_logging(config_or_path, is_cp_file=True): diff --git a/oc/od/orchestrator.py b/oc/od/orchestrator.py index 2280209..c8cc194 100755 --- a/oc/od/orchestrator.py +++ b/oc/od/orchestrator.py @@ -1160,9 +1160,11 @@ def build_volumes_home( self, authinfo:AuthInfo, userinfo:AuthUser, volume_type: self.logger.debug(f"homedirectorytype is {homedirectorytype}") subpath_name = oc.auth.namedlib.normalize_name( userinfo.userid ) self.logger.debug(f"subpath_name is {subpath_name}") - user_homedirectory = self.get_user_homedirectory(authinfo, userinfo) - self.logger.debug(f"user_homedirectory is {user_homedirectory}") - + user_homedirectory = os.path.join( self.get_user_homedirectory(authinfo, userinfo), + oc.od.settings.desktop.get('appendpathtomounthomevolume','') ) + user_homedirectory = os.path.normpath( user_homedirectory ) + self.logger.debug( f"user_homedirectory mounts home volume to {user_homedirectory}" ) + # set default value # home is emptyDir # cache is emptyDir Memory @@ -1173,7 +1175,7 @@ def build_volumes_home( self, authinfo:AuthInfo, userinfo:AuthUser, volume_type: # Take care if this is a pod application the .cache is empty self.logger.debug( f"map ~/.cache to emptyDir Memory is {oc.od.settings.desktop.get('homedirdotcachetoemptydir')}" ) if oc.od.settings.desktop.get('homedirdotcachetoemptydir') is True : - dotcache_user_homedirectory = user_homedirectory + '/.cache' + dotcache_user_homedirectory = self.get_user_homedirectory(authinfo, userinfo) + '/.cache' self.logger.debug( f"map {dotcache_user_homedirectory} to emptyDir medium Memory" ) volumes['cache'] = { 'name': 'cache', 'emptyDir': { 'medium': 'Memory', 'sizeLimit': '8Gi' } } volumes_mount['cache'] = { 'name': 'cache', 'mountPath': dotcache_user_homedirectory } @@ -1184,18 +1186,16 @@ def build_volumes_home( self, authinfo:AuthInfo, userinfo:AuthUser, volume_type: # now ovewrite home values if homedirectorytype == 'persistentVolumeClaim': - claimName = None # None is the default value, nothing to do - if isinstance( oc.od.settings.desktop['persistentvolumeclaim'], str): # oc.od.settings.desktop['persistentvolumeclaim'] is the name of the PVC - # it must exists - # there is only one PVC for all users + # in this case, there is only one shared PVC for all users + # and it must already exists if volume_type in [ 'pod_desktop', 'pod_application' ] : claimName = oc.od.settings.desktop['persistentvolumeclaim'] - if isinstance( oc.od.settings.desktop['persistentvolumeclaim'], dict): - # oc.od.settings.desktop['persistentvolumeclaim'] must be created by pyos + elif isinstance( oc.od.settings.desktop['persistentvolumeclaim'], dict): + # oc.od.settings.desktop['persistentvolumeclaim'] must be created by pyos if volume_type in [ 'pod_desktop', 'pod_application' ] : # create a pvc to store desktop volume persistentvolume = copy.deepcopy( oc.od.settings.desktop['persistentvolume'] ) @@ -3024,6 +3024,7 @@ def getPodIPAddress( self, pod_name:str )->str: IPAddress = myPod.status.pod_ip except Exception as e: self.logger.error( e ) + self.logger.debug( f"pod_IPAddress is {IPAddress}" ) return IPAddress @@ -3372,11 +3373,8 @@ def createdesktop(self, authinfo:AuthInfo, userinfo:AuthUser, **kwargs)-> ODDesk timeout_seconds=oc.od.settings.desktop['K8S_CREATE_POD_TIMEOUT_SECONDS'], field_selector=f'involvedObject.name={pod_name}'): - # safe type test event is a dict - if not isinstance(event, dict ): continue - # safe type test event object is a CoreV1Event - if not isinstance(event.get('object'), CoreV1Event ): continue - + if not isinstance(event, dict ): continue # safe type test event is a dict + if not isinstance(event.get('object'), CoreV1Event ): continue # safe type test event object is a CoreV1Event event_object = event.get('object') self.logger.debug(f"{event_object.type} reason={event_object.reason} message={event_object.message}") self.on_desktoplaunchprogress( f"b.{event_object.message}" ) @@ -3406,7 +3404,7 @@ def createdesktop(self, authinfo:AuthInfo, userinfo:AuthUser, **kwargs)-> ODDesk if event_object.reason == 'Pulled': pulled_counter = pulled_counter + 1 self.logger.debug( f"Event Pulled received pulled_counter={pulled_counter}") - # if all image are pulled pass + # if all images are pulled self.logger.debug( f"counter pulled_counter={pulled_counter} expected_containers_len={expected_containers_len}") if pulled_counter >= expected_containers_len : self.logger.debug( f"counter pulled_counter={pulled_counter} >= expected_containers_len={expected_containers_len}") @@ -3416,20 +3414,19 @@ def createdesktop(self, authinfo:AuthInfo, userinfo:AuthUser, **kwargs)-> ODDesk self.on_desktoplaunchprogress(f"b.Your pod {pod.metadata.name} gets ip address {pod_IPAddress} from network plugin") self.logger.debug( f"stop watching event list_namespaced_event for pod {pod.metadata.name} ") w.stop() - elif event_object.reason == 'Started': pod_IPAddress = self.getPodIPAddress( pod.metadata.name ) if isinstance( pod_IPAddress, str ): self.logger.debug( f"{pod.metadata.name} has an ip address: {pod_IPAddress}") - self.on_desktoplaunchprogress(f"b.Your pod {pod.metadata.name} gets ip address {pod_IPAddress} from network plugin") + self.on_desktoplaunchprogress(f"b.Your pod {pod.metadata.name} gets ip address {pod_IPAddress} from network plugin") self.logger.debug( f"counter pulled_counter={pulled_counter} expected_containers_len={expected_containers_len}") if pulled_counter >= expected_containers_len : self.logger.debug( f"counter pulled_counter={pulled_counter} >= expected_containers_len={expected_containers_len}") self.logger.debug( f"stop watching event list_namespaced_event for pod {pod.metadata.name} ") w.stop() - + """ - c = self.getcontainerfromPod( self.graphicalcontainernameprefix, myPod ) + c = self.getcontainerfromPod( self.graphicalcontainernameprefix, pod ) if isinstance( c, V1ContainerStatus ): startedmsg = self.getPodStartedMessage(self.graphicalcontainernameprefix, myPod, event_object) self.on_desktoplaunchprogress( startedmsg ) @@ -3443,7 +3440,7 @@ def createdesktop(self, authinfo:AuthInfo, userinfo:AuthUser, **kwargs)-> ODDesk self.on_desktoplaunchprogress(f"b.Your pod gets event {event_object.message or event_object.reason}") # fix for https://github.com/abcdesktopio/oc.user/issues/52 # this is not an error - # w.stop() + w.stop() # return f"{event_object.reason} {event_object.message}" else: @@ -3466,12 +3463,9 @@ def createdesktop(self, authinfo:AuthInfo, userinfo:AuthUser, **kwargs)-> ODDesk self.logger.error( f"event type is {type( event )}, and should be a dict, skipping event") continue - # event dict must contain a type - event_type = event.get('type') - # event dict must contain a pod object - pod_event = event.get('object') - # if podevent type must be a V1Pod, we use kubeapi.list_namespaced_pod - if not isinstance( pod_event, V1Pod ): continue + event_type = event.get('type') # event dict must contain a type + pod_event = event.get('object') # event dict must contain a pod object + if not isinstance( pod_event, V1Pod ): continue # if podevent type must be a V1Pod if not isinstance( pod_event.status, V1PodStatus ): continue # self.on_desktoplaunchprogress( f"b.Your {pod_event.kind.lower()} is {event_type.lower()}") @@ -3504,7 +3498,7 @@ def createdesktop(self, authinfo:AuthInfo, userinfo:AuthUser, **kwargs)-> ODDesk self.logger.error(f"The pod {pod_event.metadata.name} is in phase={pod_event.status.phase} stop watching" ) w.stop() - self.logger.debug(f"watch list_namespaced_pod created, the pod is no more in Pending phase phase={pod_event.status.phase}" ) + self.logger.debug(f"watch list_namespaced_pod created, the pod is no more in Pending phase" ) # read pod again self.logger.debug(f"read_namespaced_pod {pod_name} again" ) @@ -4481,15 +4475,16 @@ def create(self, myDesktop, app, authinfo, userinfo={}, userargs=None, **kwargs break if c.state.waiting.reason == 'Pulling': send_previous_pulling_message = True - data['message'] = f"Installing {app.get('name')}, please wait" + data['message'] = f"{c.state.waiting.reason} {app.get('name')}, please wait" self.orchestrator.notify_user( myDesktop, 'container', data ) elif c.state.waiting.reason == 'Pulled': if send_previous_pulling_message is True: - data['message'] = f"{app.get('name')} is installed" + data['message'] = f"{app.get('name')} is {c.state.waiting.reason}" self.orchestrator.notify_user( myDesktop, 'container', data ) else: - data['message'] = c.state.waiting.message - self.orchestrator.notify_user( myDesktop, 'container', data ) + if send_previous_pulling_message is True: + data['message'] = c.state.waiting.reason + self.orchestrator.notify_user( myDesktop, 'container', data ) if event.get('type') == 'ERROR': self.logger.error( f"{event.get('type')} object={type(event.get('object'))}") diff --git a/oc/od/settings.py b/oc/od/settings.py index f2c54de..665a4d7 100755 --- a/oc/od/settings.py +++ b/oc/od/settings.py @@ -17,8 +17,6 @@ # supported image format ABCDESKTOP_IMAGE_FORMAT_RELEASE = '3.0' -defaultConfigurationFilename = 'od.config' - config = {} # use for application config and global config gconfig = {} # use for global config @@ -329,7 +327,7 @@ def init_desktop(): # default secret path desktop['secretsrootdirectory'] = gconfig.get('desktop.secretsrootdirectory', '/var/secrets/') - desktop['release'] = gconfig.get('desktop.release', '3.1') + desktop['release'] = gconfig.get('desktop.release', '3.2') # # in release 3.1 # desktop['secretslocalaccount'] = gconfig.get('desktop.secretslocalaccount', '/etc/localaccount') @@ -355,10 +353,13 @@ def init_desktop(): desktop['prestopexeccommand'] = gconfig.get('desktop.prestopexeccommand', [ "/bin/bash", "-c", "rm -rf ~/{*,.*}" ] ) desktop['persistentvolumeclaim'] = gconfig.get('desktop.persistentvolumeclaim') or gconfig.get('desktop.persistentvolumeclaimspec') desktop['persistentvolume'] = gconfig.get('desktop.persistentvolume') or gconfig.get('desktop.persistentvolumespec') - desktop['persistentvolumeclaimforcesubpath'] = gconfig.get('desktop.persistentvolumeclaimforcesubpath',False) + desktop['homedirdotcachetoemptydir']= gconfig.get('desktop.homedirdotcachetoemptydir', False) desktop['removepersistentvolume'] = gconfig.get('desktop.removepersistentvolume', False) + desktop['appendpathtomounthomevolume'] = gconfig.get('desktop.appendpathtomounthomevolume') desktop['removepersistentvolumeclaim'] = gconfig.get('desktop.removepersistentvolumeclaim', False) - desktop['homedirdotcachetoemptydir']= gconfig.get('desktop.homedirdotcachetoemptydir', True) + desktop['persistentvolumeclaimforcesubpath'] = gconfig.get('desktop.persistentvolumeclaimforcesubpath',False) + + desktop['K8S_BOUND_PVC_TIMEOUT_SECONDS'] = gconfig.get('K8S_BOUND_PVC_TIMEOUT_SECONDS', 60 ) desktop['K8S_BOUND_PVC_MAX_EVENT'] = gconfig.get('K8S_BOUND_PVC_MAX_EVENT', 5 ) @@ -685,15 +686,31 @@ def init_executeclass(): def get_default_appdict(): return dock + +def get_configuration_file_name(): + """get_configuration_file_name + + Returns: + str: name of the config file 'od.config' by default or read 'OD_CONFIG_PATH' os.environ + """ + configuration_file_name = os.environ.get('OD_CONFIG_PATH', 'od.config') + return configuration_file_name + def load_config(): global config global gconfig - configpath = os.environ.get('OD_CONFIG_PATH', defaultConfigurationFilename) + configpath = get_configuration_file_name() logger.info(f"Loading configuration file {configpath}") try: config = Config(configpath) - gconfig = config.get('global', {}) # = cherrypy.gconfig + if isinstance( config.get('global'), dict ): + logger.info(f"config file contains [global] entry (ini file format)") + gconfig = config.get('global', {}) # = cherrypy.gconfig + else: + logger.info(f"config file does not set [global] entry") + logger.info(f"config file is not a ini file format, use json") + except Exception as e: logger.error(f"Failed to load configuration file {configpath} {e}") exit(-1) diff --git a/od.py b/od.py index ecd36aa..a227d19 100755 --- a/od.py +++ b/od.py @@ -27,18 +27,12 @@ import oc.od.services as services # Load logging config ASAP ! -oc.logging.configure( config_or_path=oc.od.settings.defaultConfigurationFilename, is_cp_file=True) +oc.logging.configure( config_or_path=settings.get_configuration_file_name(), is_cp_file=True) logger = logging.getLogger(__name__) -# define virtual path used -# app_virtual_path is for application -# img_virtual_path is for icon static file -app_virtual_path = '/API' -img_virtual_path = '/img' - # define each configration for API -# app_config is the core servive -# img_config is a dummy app used to serve icon static file +# app_config is the core service +# img_config is file service to send icon static file # Allow (partial) case-insensivity in URLs class APIDispatcher(Dispatcher): @@ -112,12 +106,6 @@ def img_handle_404_application(status, message, traceback, version): # # img class to serve static files # for example icon files for applications -@cherrypy.config(**{ - 'tools.staticdir.on' : True, - 'tools.staticdir.dir': '/var/pyos/img', # relative path not allowed - 'tools.allow.methods': [ 'GET' ], # HTTP GET only for images - 'error_page.404' : img_handle_404_application -}) class IMG(object): def __init__(self): """ init IMG static files @@ -232,14 +220,21 @@ def stop(self): def run_server(): logger.info("Starting cherrypy service...") # update config for cherrypy - cherrypy.config.update(settings.defaultConfigurationFilename) - settings.config['/']['request.dispatch'] = APIDispatcher() # can't be set with @cherrypy.config decorator + cherrypy.config.update(settings.get_configuration_file_name()) + # cherrypy.config['/'] + # APIConfig = { '/': { 'request.dispatch': APIDispatcher() } } + # settings.config['/']['request.dispatch'] = APIDispatcher() # can't be set with @cherrypy.config decorator # set auth tools cherrypy.tools.auth = services.services.auth # set /API - cherrypy.tree.mount( API(settings.controllers), app_virtual_path, settings.config ) + cherrypy.tree.mount( API(settings.controllers), '/API', settings.config ) # set /IMG - cherrypy.tree.mount(IMG(), img_virtual_path, config={} ) # no config for img, use class config + cherrypy.tree.mount( IMG(), '/img', config= + { '/img': { 'tools.staticdir.on' : True, + 'tools.staticdir.dir': '/var/pyos/img', # relative path not allowed + 'tools.allow.methods': [ 'GET' ], # HTTP GET only for images + 'error_page.404' : img_handle_404_application # overwrite 404 to default icon + } } ) odthread_watcher = ODCherryWatcher(cherrypy.engine) odthread_watcher.subscribe()