Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

appendpathtomounthomevolume #15

Merged
merged 7 commits into from
Mar 21, 2024
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
18 changes: 11 additions & 7 deletions oc/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
59 changes: 27 additions & 32 deletions oc/od/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
Expand All @@ -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'] )
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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}" )
Expand Down Expand Up @@ -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}")
Expand All @@ -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 )
Expand All @@ -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:
Expand All @@ -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()}")
Expand Down Expand Up @@ -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" )
Expand Down Expand Up @@ -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'))}")
Expand Down
31 changes: 24 additions & 7 deletions oc/od/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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')
Expand All @@ -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 )
Expand Down Expand Up @@ -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)
Expand Down
33 changes: 14 additions & 19 deletions od.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down