<s> from __future__ import print_function , unicode_literals <EOL> import time <EOL> import datetime <EOL> import Queue <EOL> import json <EOL> import weblab . configuration_doc as configuration_doc <EOL> from voodoo . typechecker import typecheck , ITERATION <EOL> from voodoo . log import logged <EOL> import voodoo . log as log <EOL> from voodoo . gen import CoordAddress <EOL> from voodoo . sessions . session_id import SessionId <EOL> import voodoo . admin_notifier as AdminNotifier <EOL> from weblab . data . experiments import ExperimentId , ExperimentInstanceId <EOL> import weblab . core . coordinator . exc as CoordExc <EOL> import weblab . core . coordinator . scheduler as Scheduler <EOL> import weblab . core . coordinator . store as TemporalInformationStore <EOL> import weblab . core . coordinator . status as coord_status <EOL> import weblab . core . coordinator . config_parser as CoordinationConfigurationParser <EOL> import weblab . core . coordinator . confirmer as Confirmer <EOL> import weblab . core . coordinator . checker_threaded as ResourcesCheckerThread <EOL> from weblab . core . coordinator . resource import Resource <EOL> FINISH_FINISHED_MESSAGE = '<STR_LIT>' <EOL> FINISH_DATA_MESSAGE = '<STR_LIT:data>' <EOL> FINISH_ASK_AGAIN_MESSAGE = '<STR_LIT>' <EOL> POST_RESERVATION_EXPIRATION_TIME = '<STR_LIT>' <EOL> DEFAULT_POST_RESERVATION_EXPIRATION_TIME = <NUM_LIT> * <NUM_LIT> <EOL> NO_SCHEDULER = '<STR_LIT>' <EOL> PRIORITY_QUEUE = '<STR_LIT>' <EOL> EXTERNAL_WEBLAB_DEUSTO = '<STR_LIT>' <EOL> ILAB_BATCH_QUEUE = '<STR_LIT>' <EOL> RESOURCES_CHECKER_NOTIFICATIONS_ENABLED = '<STR_LIT>' <EOL> DEFAULT_RESOURCES_CHECKER_NOTIFICATIONS_ENABLED = False <EOL> CORE_SCHEDULING_SYSTEMS = '<STR_LIT>' <EOL> CORE_SCHEDULER_AGGREGATORS = '<STR_LIT>' <EOL> CORE_SERVER_URL = '<STR_LIT>' <EOL> RESOURCES_CHECKER_FREQUENCY = '<STR_LIT>' <EOL> DEFAULT_RESOURCES_CHECKER_FREQUENCY = <NUM_LIT:30> <EOL> RESOURCES_CHECKER_GENERAL_RECIPIENTS = '<STR_LIT>' <EOL> DEFAULT_RESOURCES_GENERAL_CHECKER_RECIPIENTS = ( ) <EOL> RESOURCES_CHECKER_PARTICULAR_RECIPIENTS = '<STR_LIT>' <EOL> DEFAULT_RESOURCES_PARTICULAR_CHECKER_RECIPIENTS = { <EOL> } <EOL> class TimeProvider ( object ) : <EOL> def get_time ( self ) : <EOL> return time . time ( ) <EOL> def get_datetime ( self ) : <EOL> return datetime . datetime . utcnow ( ) <EOL> class AbstractCoordinator ( object ) : <EOL> CoordinatorTimeProvider = TimeProvider <EOL> def __init__ ( self , data_manager , locator , cfg_manager , ConfirmerClass ) : <EOL> if ConfirmerClass is None : <EOL> ConfirmerClass = Confirmer . ReservationConfirmer <EOL> self . cfg_manager = cfg_manager <EOL> self . _data_manager = data_manager <EOL> self . core_server_url = self . cfg_manager . get_value ( CORE_SERVER_URL ) <EOL> self . notifier = AdminNotifier . AdminNotifier ( self . cfg_manager ) <EOL> self . notifications_enabled = self . cfg_manager . get_value ( RESOURCES_CHECKER_NOTIFICATIONS_ENABLED , DEFAULT_RESOURCES_CHECKER_NOTIFICATIONS_ENABLED ) <EOL> self . locator = locator <EOL> self . confirmer = ConfirmerClass ( self , locator ) <EOL> self . time_provider = self . CoordinatorTimeProvider ( ) <EOL> self . initial_store = TemporalInformationStore . InitialTemporalInformationStore ( ) <EOL> self . finished_store = TemporalInformationStore . FinishTemporalInformationStore ( ) <EOL> self . completed_store = TemporalInformationStore . CompletedInformationStore ( ) <EOL> self . finished_reservations_store = Queue . Queue ( ) <EOL> self . _initialize_managers ( ) <EOL> self . schedulers = { } <EOL> scheduling_systems = cfg_manager . get_value ( CORE_SCHEDULING_SYSTEMS ) <EOL> for resource_type_name in scheduling_systems : <EOL> scheduling_system , arguments = scheduling_systems [ resource_type_name ] <EOL> if not scheduling_system in self . SCHEDULING_SYSTEMS : <EOL> raise CoordExc . UnregisteredSchedulingSystemError ( "<STR_LIT>" % scheduling_system ) <EOL> SchedulingSystemClass = self . SCHEDULING_SYSTEMS [ scheduling_system ] <EOL> data_manager = self . _data_manager <EOL> generic_scheduler_arguments = Scheduler . GenericSchedulerArguments ( <EOL> cfg_manager = self . cfg_manager , <EOL> resource_type_name = resource_type_name , <EOL> reservations_manager = self . reservations_manager , <EOL> resources_manager = self . resources_manager , <EOL> confirmer = self . confirmer , <EOL> data_manager = data_manager , <EOL> time_provider = self . time_provider , <EOL> core_server_url = self . core_server_url , <EOL> initial_store = self . initial_store , <EOL> finished_store = self . finished_store , <EOL> completed_store = self . completed_store , <EOL> post_reservation_data_manager = self . post_reservation_data_manager <EOL> ) <EOL> self . schedulers [ resource_type_name ] = SchedulingSystemClass ( generic_scheduler_arguments , ** arguments ) <EOL> self . aggregators = { <EOL> } <EOL> coordination_configuration_parser = CoordinationConfigurationParser . CoordinationConfigurationParser ( cfg_manager ) <EOL> resource_types_per_experiment_id = coordination_configuration_parser . parse_resources_for_experiment_ids ( ) <EOL> aggregators_configuration = self . cfg_manager . get_value ( CORE_SCHEDULER_AGGREGATORS , { } ) <EOL> for experiment_id_str in resource_types_per_experiment_id : <EOL> generic_scheduler_arguments = Scheduler . GenericSchedulerArguments ( <EOL> cfg_manager = self . cfg_manager , <EOL> resource_type_name = None , <EOL> reservations_manager = self . reservations_manager , <EOL> resources_manager = self . resources_manager , <EOL> confirmer = self . confirmer , <EOL> data_manager = self . _data_manager , <EOL> time_provider = self . time_provider , <EOL> core_server_url = self . core_server_url , <EOL> initial_store = self . initial_store , <EOL> finished_store = self . finished_store , <EOL> completed_store = self . completed_store , <EOL> post_reservation_data_manager = self . post_reservation_data_manager <EOL> ) <EOL> resource_type_names = resource_types_per_experiment_id [ experiment_id_str ] <EOL> try : <EOL> aggregated_schedulers = { } <EOL> for resource_type_name in resource_type_names : <EOL> aggregated_schedulers [ resource_type_name ] = self . schedulers [ resource_type_name ] <EOL> except KeyError , ke : <EOL> raise Exception ( "<STR_LIT>" % ( ke , CORE_SCHEDULING_SYSTEMS ) ) <EOL> particular_configuration = aggregators_configuration . get ( experiment_id_str ) <EOL> aggregator = self . AGGREGATOR ( generic_scheduler_arguments , ExperimentId . parse ( experiment_id_str ) , aggregated_schedulers , particular_configuration ) <EOL> self . aggregators [ experiment_id_str ] = aggregator <EOL> clean = cfg_manager . get ( '<STR_LIT>' ) == <NUM_LIT:0> <EOL> post_reservation_expiration_time = cfg_manager . get_value ( POST_RESERVATION_EXPIRATION_TIME , DEFAULT_POST_RESERVATION_EXPIRATION_TIME ) <EOL> self . expiration_delta = datetime . timedelta ( seconds = post_reservation_expiration_time ) <EOL> if clean : <EOL> resources_checker_frequency = cfg_manager . get_value ( RESOURCES_CHECKER_FREQUENCY , DEFAULT_RESOURCES_CHECKER_FREQUENCY ) <EOL> ResourcesCheckerThread . set_coordinator ( self , resources_checker_frequency ) <EOL> self . _clean ( ) <EOL> coordination_configuration_parser = CoordinationConfigurationParser . CoordinationConfigurationParser ( self . cfg_manager ) <EOL> configuration = coordination_configuration_parser . parse_configuration ( ) <EOL> for laboratory_server_coord_address_str in configuration : <EOL> experiment_instance_config = configuration [ laboratory_server_coord_address_str ] <EOL> for experiment_instance_id in experiment_instance_config : <EOL> resource = experiment_instance_config [ experiment_instance_id ] <EOL> self . add_experiment_instance_id ( laboratory_server_coord_address_str , experiment_instance_id , resource ) <EOL> self . _initial_clean ( coordination_configuration_parser ) <EOL> def _initialize_managers ( self ) : <EOL> pass <EOL> def _initial_clean ( self , coordination_configuration_parser ) : <EOL> pass <EOL> @ typecheck ( basestring , ExperimentInstanceId , Resource ) <EOL> @ logged ( ) <EOL> def add_experiment_instance_id ( self , laboratory_coord_address , experiment_instance_id , resource ) : <EOL> self . resources_manager . add_experiment_instance_id ( laboratory_coord_address , experiment_instance_id , resource ) <EOL> @ typecheck ( Resource ) <EOL> def _get_scheduler_per_resource ( self , resource ) : <EOL> return self . schedulers [ resource . resource_type ] <EOL> @ typecheck ( basestring ) <EOL> def _get_scheduler_aggregator_per_reservation ( self , reservation_id ) : <EOL> experiment_id = self . reservations_manager . get_experiment_id ( reservation_id ) <EOL> return self . _get_scheduler_aggregator ( experiment_id ) <EOL> @ typecheck ( ExperimentId ) <EOL> def _get_scheduler_aggregator ( self , experiment_id ) : <EOL> experiment_id_str = experiment_id . to_weblab_str ( ) <EOL> aggregator = self . aggregators . get ( experiment_id_str ) <EOL> if aggregator is None : <EOL> raise CoordExc . ExperimentNotFoundError ( "<STR_LIT>" % ( experiment_id_str ) ) <EOL> return aggregator <EOL> @ logged ( ) <EOL> def list_experiments ( self ) : <EOL> return self . resources_manager . list_experiments ( ) <EOL> @ logged ( ) <EOL> def list_laboratories_addresses ( self ) : <EOL> return self . resources_manager . list_laboratories_addresses ( ) <EOL> @ logged ( ) <EOL> def list_resource_types ( self ) : <EOL> return self . schedulers . keys ( ) <EOL> @ typecheck ( ExperimentId ) <EOL> @ logged ( ) <EOL> def list_sessions ( self , experiment_id ) : <EOL> """<STR_LIT>""" <EOL> reservation_ids = self . reservations_manager . list_sessions ( experiment_id ) <EOL> result = { } <EOL> for reservation_id in reservation_ids : <EOL> try : <EOL> aggregator = self . _get_scheduler_aggregator_per_reservation ( reservation_id ) <EOL> best_reservation_status = aggregator . get_reservation_status ( reservation_id ) <EOL> except CoordExc . CoordinatorError : <EOL> continue <EOL> result [ reservation_id ] = best_reservation_status <EOL> return result <EOL> @ typecheck ( ExperimentInstanceId , ITERATION ( basestring ) ) <EOL> @ logged ( ) <EOL> def mark_experiment_as_broken ( self , experiment_instance_id , messages ) : <EOL> resource_instance = self . resources_manager . get_resource_instance_by_experiment_instance_id ( experiment_instance_id ) <EOL> return self . mark_resource_as_broken ( resource_instance , messages ) <EOL> @ typecheck ( Resource ) <EOL> @ logged ( ) <EOL> def mark_resource_as_fixed ( self , resource_instance ) : <EOL> anything_changed = self . resources_manager . mark_resource_as_fixed ( resource_instance ) <EOL> if anything_changed : <EOL> log . log ( AbstractCoordinator , log . level . Warning , <EOL> "<STR_LIT>" % resource_instance ) <EOL> if self . notifications_enabled : <EOL> return self . _notify_experiment_status ( '<STR_LIT>' , resource_instance ) <EOL> return { } <EOL> @ typecheck ( basestring , Resource , ITERATION ( basestring ) ) <EOL> def _notify_experiment_status ( self , new_status , resource_instance , messages = [ ] ) : <EOL> experiment_instance_ids = self . resources_manager . list_experiment_instance_ids_by_resource ( resource_instance ) <EOL> if new_status == '<STR_LIT>' : <EOL> what = '<STR_LIT>' <EOL> else : <EOL> what = '<STR_LIT>' <EOL> body = """<STR_LIT>""" % ( <EOL> resource_instance , new_status , what ) <EOL> for experiment_instance_id in experiment_instance_ids : <EOL> body += ( "<STR_LIT>" % experiment_instance_id . to_weblab_str ( ) ) <EOL> if len ( messages ) > <NUM_LIT:0> : <EOL> body += "<STR_LIT>" % ( messages , self . core_server_url ) <EOL> recipients = self . _retrieve_recipients ( experiment_instance_ids ) <EOL> if len ( recipients ) : <EOL> return { tuple ( sorted ( recipients ) ) : [ ( body , resource_instance ) ] } <EOL> else : <EOL> return { } <EOL> def notify_status ( self , notifications ) : <EOL> """<STR_LIT>""" <EOL> for recipients in notifications : <EOL> current_notifications = list ( notifications [ recipients ] ) <EOL> current_notifications . sort ( lambda ( body1 , resource_instance1 ) , ( body2 , resource_instance2 ) : cmp ( unicode ( resource_instance1 ) , unicode ( resource_instance2 ) ) ) <EOL> resources = map ( lambda ( body , resource_instance ) : resource_instance , current_notifications ) <EOL> bodies = map ( lambda ( body , resource_instance ) : body , current_notifications ) <EOL> subject = "<STR_LIT>" % len ( resources ) <EOL> shown = <NUM_LIT:2> <EOL> resource_types = list ( set ( map ( lambda resource : unicode ( resource . resource_type ) , resources ) ) ) <EOL> subject += '<STR_LIT:U+002CU+0020>' . join ( resource_types [ : shown ] ) <EOL> if len ( resource_types ) > shown : <EOL> subject += '<STR_LIT>' <EOL> if len ( bodies ) > <NUM_LIT:1> : <EOL> body = "<STR_LIT>" % len ( bodies ) <EOL> else : <EOL> body = "<STR_LIT>" <EOL> body += '<STR_LIT>' . join ( bodies ) <EOL> self . notifier . notify ( recipients = recipients , body = body , subject = subject ) <EOL> def _retrieve_recipients ( self , experiment_instance_ids ) : <EOL> recipients = ( ) <EOL> server_admin = self . cfg_manager . get_value ( configuration_doc . SERVER_ADMIN , None ) <EOL> if server_admin is not None : <EOL> if server_admin . find ( "<STR_LIT:U+002C>" ) : <EOL> server_admins = tuple ( server_admin . replace ( "<STR_LIT:U+0020>" , "<STR_LIT>" ) . split ( "<STR_LIT:U+002C>" ) ) <EOL> else : <EOL> server_admins = ( server_admin , ) <EOL> recipients += server_admins <EOL> general_recipients = self . cfg_manager . get_value ( RESOURCES_CHECKER_GENERAL_RECIPIENTS , DEFAULT_RESOURCES_GENERAL_CHECKER_RECIPIENTS ) <EOL> recipients += tuple ( general_recipients ) <EOL> for experiment_instance_id in experiment_instance_ids : <EOL> particular_recipients = self . cfg_manager . get_value ( RESOURCES_CHECKER_PARTICULAR_RECIPIENTS , DEFAULT_RESOURCES_PARTICULAR_CHECKER_RECIPIENTS ) <EOL> experiment_particular_recipients = particular_recipients . get ( experiment_instance_id . to_weblab_str ( ) , ( ) ) <EOL> recipients += tuple ( experiment_particular_recipients ) <EOL> return tuple ( set ( recipients ) ) <EOL> @ typecheck ( ExperimentId , ( float , int ) , int , bool , ( dict , basestring ) , dict , dict ) <EOL> @ logged ( ) <EOL> def reserve_experiment ( self , experiment_id , time , priority , initialization_in_accounting , client_initial_data , request_info , consumer_data ) : <EOL> """<STR_LIT>""" <EOL> reservation_id = self . reservations_manager . create ( experiment_id , client_initial_data , json . dumps ( request_info ) , self . time_provider . get_datetime ) <EOL> aggregator = self . _get_scheduler_aggregator ( experiment_id ) <EOL> return aggregator . reserve_experiment ( reservation_id , experiment_id , time , priority , initialization_in_accounting , client_initial_data , request_info ) , reservation_id <EOL> @ typecheck ( basestring ) <EOL> @ logged ( ) <EOL> def get_reservation_status ( self , reservation_id ) : <EOL> reservation_id_without_route = reservation_id . split ( '<STR_LIT:;>' ) [ <NUM_LIT:0> ] <EOL> try : <EOL> aggregator = self . _get_scheduler_aggregator_per_reservation ( reservation_id_without_route ) <EOL> return aggregator . get_reservation_status ( reservation_id_without_route ) <EOL> except CoordExc . ExpiredSessionError : <EOL> reservation_status = self . post_reservation_data_manager . find ( reservation_id_without_route ) <EOL> if reservation_status is not None : <EOL> return reservation_status <EOL> raise <EOL> except : <EOL> import traceback <EOL> traceback . print_exc ( ) <EOL> raise <EOL> def is_post_reservation ( self , reservation_id ) : <EOL> return self . post_reservation_data_manager . find ( reservation_id ) is not None <EOL> @ typecheck ( CoordAddress , ExperimentId , basestring , basestring , SessionId , ( basestring , type ( None ) ) , datetime . datetime , datetime . datetime , basestring , dict ) <EOL> @ logged ( ) <EOL> def confirm_experiment ( self , experiment_coordaddress , experiment_id , reservation_id , lab_coordaddress_str , lab_session_id , server_initialization_response , initial_time , end_time , resource_type_name , exp_info ) : <EOL> default_still_initialing = False <EOL> default_batch = False <EOL> default_initial_configuration = "<STR_LIT:{}>" <EOL> if server_initialization_response is None or server_initialization_response == '<STR_LIT>' or server_initialization_response == '<STR_LIT>' : <EOL> still_initializing = default_still_initialing <EOL> batch = default_batch <EOL> initial_configuration = default_initial_configuration <EOL> else : <EOL> try : <EOL> response = json . loads ( server_initialization_response ) <EOL> still_initializing = response . get ( '<STR_LIT>' , default_still_initialing ) <EOL> batch = response . get ( '<STR_LIT>' , default_batch ) <EOL> initial_configuration = response . get ( '<STR_LIT>' , default_initial_configuration ) <EOL> except Exception as e : <EOL> log . log ( AbstractCoordinator , log . level . Error , "<STR_LIT>" % ( e , server_initialization_response ) ) <EOL> log . log_exc ( AbstractCoordinator , log . level . Warning ) <EOL> still_initializing = default_still_initialing <EOL> batch = default_batch <EOL> initial_configuration = default_initial_configuration <EOL> aggregator = self . _get_scheduler_aggregator_per_reservation ( reservation_id ) <EOL> removed = aggregator . assign_single_scheduler ( reservation_id , resource_type_name , True ) <EOL> if removed is None : <EOL> return <EOL> serialized_request_info , serialized_client_initial_data = self . reservations_manager . get_request_info_and_client_initial_data ( reservation_id ) <EOL> request_info = json . loads ( serialized_request_info ) <EOL> initial_information_entry = TemporalInformationStore . InitialInformationEntry ( <EOL> reservation_id , experiment_id , experiment_coordaddress , <EOL> initial_configuration , initial_time , end_time , request_info , <EOL> serialized_client_initial_data ) <EOL> self . initial_store . put ( initial_information_entry ) <EOL> now = self . time_provider . get_datetime ( ) <EOL> self . post_reservation_data_manager . create ( reservation_id , now , now + self . expiration_delta , json . dumps ( initial_configuration ) ) <EOL> if still_initializing : <EOL> raise NotImplementedError ( "<STR_LIT>" ) <EOL> aggregator = self . _get_scheduler_aggregator_per_reservation ( reservation_id ) <EOL> aggregator . confirm_experiment ( reservation_id , lab_session_id , initial_configuration , exp_info ) <EOL> if batch : <EOL> self . finish_reservation ( reservation_id ) <EOL> return <EOL> self . confirmer . enqueue_should_finish ( lab_coordaddress_str , lab_session_id , reservation_id ) <EOL> @ typecheck ( basestring , basestring , ( basestring , type ( None ) ) , ExperimentInstanceId , ( basestring , type ( None ) ) , datetime . datetime , datetime . datetime ) <EOL> @ logged ( ) <EOL> def confirm_resource_disposal ( self , lab_coordaddress , reservation_id , lab_session_id , experiment_instance_id , experiment_response , initial_time , end_time ) : <EOL> experiment_finished = True <EOL> information_to_store = None <EOL> time_remaining = <NUM_LIT:0.5> <EOL> if experiment_response is None or experiment_response == '<STR_LIT>' or experiment_response == '<STR_LIT>' : <EOL> pass <EOL> else : <EOL> try : <EOL> response = json . loads ( experiment_response ) <EOL> experiment_finished = response . get ( FINISH_FINISHED_MESSAGE , experiment_finished ) <EOL> time_remaining = response . get ( FINISH_ASK_AGAIN_MESSAGE , time_remaining ) <EOL> information_to_store = response . get ( FINISH_DATA_MESSAGE , information_to_store ) <EOL> except Exception as e : <EOL> log . log ( AbstractCoordinator , log . level . Error , "<STR_LIT>" % ( e , experiment_response ) ) <EOL> log . log_exc ( AbstractCoordinator , log . level . Warning ) <EOL> if not experiment_finished : <EOL> time . sleep ( time_remaining ) <EOL> self . confirmer . enqueue_free_experiment ( lab_coordaddress , reservation_id , lab_session_id , experiment_instance_id ) <EOL> return <EOL> else : <EOL> self . post_reservation_data_manager . finish ( reservation_id , json . dumps ( information_to_store ) ) <EOL> try : <EOL> self . _release_resource_instance ( experiment_instance_id ) <EOL> finally : <EOL> self . finished_store . put ( reservation_id , information_to_store , initial_time , end_time ) <EOL> self . post_reservation_data_manager . clean_expired ( ) <EOL> @ typecheck ( basestring ) <EOL> @ logged ( ) <EOL> def finish_reservation ( self , reservation_id ) : <EOL> reservation_id = reservation_id . split ( '<STR_LIT:;>' ) [ <NUM_LIT:0> ] <EOL> if self . reservations_manager . initialize_deletion ( reservation_id ) : <EOL> self . finished_reservations_store . put ( SessionId ( reservation_id ) ) <EOL> try : <EOL> aggregator = self . _get_scheduler_aggregator_per_reservation ( reservation_id ) <EOL> aggregator . finish_reservation ( reservation_id ) <EOL> self . _delete_reservation ( reservation_id ) <EOL> except CoordExc . ExpiredSessionError : <EOL> log . log ( AbstractCoordinator , log . level . Info , "<STR_LIT>" % reservation_id ) <EOL> finally : <EOL> self . reservations_manager . clean_deletion ( reservation_id ) <EOL> @ typecheck ( basestring , SessionId , basestring , ( int , float ) ) <EOL> @ logged ( ) <EOL> def confirm_should_finish ( self , lab_coordaddress_str , lab_session_id , reservation_id , experiment_response ) : <EOL> try : <EOL> current_status = self . get_reservation_status ( reservation_id ) <EOL> if not isinstance ( current_status , ( coord_status . LocalReservedStatus , coord_status . RemoteReservedStatus ) ) : <EOL> return <EOL> except CoordExc . CoordinatorError : <EOL> return <EOL> if experiment_response == <NUM_LIT:0> : <EOL> return <EOL> if experiment_response < <NUM_LIT:0> : <EOL> self . finish_reservation ( reservation_id ) <EOL> return <EOL> time . sleep ( experiment_response ) <EOL> self . confirmer . enqueue_should_finish ( lab_coordaddress_str , lab_session_id , reservation_id ) <EOL> @ logged ( ) <EOL> def stop ( self ) : <EOL> for scheduler in self . schedulers . values ( ) : <EOL> scheduler . stop ( ) </s>
