B Wò`:Mã@s‚ddlZddlmZddlZddlZddlZddlmZmZddlm Z ddl Z ddl Z ddl Z ej ejd�Gdd„deƒZdS)éN)Ú ClientError)ÚdatetimeÚ timedelta)Úuuid4)Úlevelc@s°eZdZdZd.dd„Zdd„Zd/d d „Zd0d d „Zdd„Zd1dd„Z dd„Z dd„Z d2dd„Z d3dd„Z d4dd„Zd5dd „Zd!d"„Zd#d$„Zd%d&„Zd'd(„Zd)d*„Zd+d,„Zd-S)6ÚFarOptaëFarOpt class used to initialize, configure and submit jobs to the back end :param framework: Currently only ortools, TO DO is to extend to other frameworks. Note that other frameworks like pyomo, DEAP, inspyred and pulp are supported :type framework: string, optional :param stackname: Points to the backend CDK stack that needs to be launched separately. Default name is faropt, but you many need to pass in another name while testing :type stackname: string, optional ÚortoolsÚfaroptc Cs”t d¡}�y$|j|d�}|ddddk�r.t d¡d|_||_|ddd }xÊ|D]Â}|d d kr†|d |_t d |j¡|d dkr¬|d |_t d|j¡|d dkrÒ|d |_ t d|j ¡|d dkrø|d |_ t d|j ¡|d dkr\|d |_ t d|j ¡q\Wd|_ d|_ Wn4tk �rd}zd|_t |¡Wdd}~XYnXdg|_||jk�rŠt d|¡n||_dS)z_Constructor method: Gets buckets and tables associated with the already launched stack Zcloudformation)Z StackNameZStacksrZ StackStatus)ZCREATE_COMPLETEZUPDATE_COMPLETEzFarOpt backend is ready!TZOutputsZ OutputKeyZ s3asyncoutputZ OutputValuezAsync Bucket: Zs3outputzBucket: Ú recipetablezRecipe Table: Újobtablez Job table: Ú lambdaoptzLambda Opt function: FNrz/Only ortools is supported for now. You entered )Úboto3ÚclientZdescribe_stacksÚloggingÚinfoÚreadyÚ stacknameÚ asyncbucketÚbucketr r r Ú configuredÚ submittedÚ ExceptionÚerrorZallowed_frameworksÚwarningÚ framework)ÚselfrrÚcfÚresponseÚoutputsÚoutputÚe©r!ú3/tmp/pip-unpacked-wheel-9of43mn_/faropt/__init__.pyÚ__init__sB                zFarOpt.__init__c Cs®t d¡d}t dd¡}xbt |¡D]T\}}}t|||ƒx<|D]4}t|ƒ| tj  ||¡tj  |d||¡¡qBWq&W|  ¡tj  |¡|_ ||_d|_t d¡dS)aRZips up a local folder containing your main.py code, and any other subfolders/files required to run your project. Make note of the output structure printed to see if all files that you need are printed.ArithmeticError :param source_dir: path to your source, such as './home/src/' :type source_dir: string zListing project files ...z source.zipÚwú/TzConfigured job!N)rrÚzipfileÚZipFileÚosÚwalkÚprintÚwriteÚpathÚjoinÚrelpathÚcloseÚabspathÚpath_file_nameÚ file_namer)rÚ source_dirr2ÚzfÚdirnameÚsubdirsÚfilesÚfilenamer!r!r"Ú configureIs    0zFarOpt.configureúFaropt SDK usercCsŒ|jr~t d¡|_|j |j¡|_ttƒƒ}|j rN|||j |j d|ddœ}n |||j |j d|j|ddœ}|jj|d�n t d¡dS) a;Adds a recipe referencing the job that you submitted (see self object params). :param recipe_name: Friendly name for your recipe :type recipe_name: string :param maintainer: Recipe author/maintainer :type source_dir: string, optional. Defaults to 'Faropt SDK User' Údynamodbz /source.zipzsee path)ÚrecipeidÚ descriptionrr,Ú maintainerÚcoder%)ÚItemzPlease configure the job first!N)rr ÚresourceÚ ddb_resourceÚTabler Ú ddb_tableÚstrrÚmicrorÚ stagedkeyrÚjobnamer2Úput_itemrr)rZ recipe_namer>ZUIDÚjobr!r!r"Ú add_recipeas&   zFarOpt.add_recipeFc Csxt d¡t d¡}tddƒ�}| |||¡WdQRXtj d¡|_ d|_ t d¡d|_ |rh|  ¡|j |d �dS) a�Runs job based on a source file in bucket/key. For example, place a source.zip in s3://bucket/key/source.zip and submit a job :param bucket: Bucket name :type bucket: string :param key: path/key on S3 that looks like path/to/s3/key/source.zip inside the bucket :type key: string :param micro: Submit a micro job. :type micro: bool zDownloading source...Ús3z/tmp/source.zipÚwbNz source.zipzConfigured job!T)rF)rrr rÚopenÚdownload_fileobjr(r,r0r1r2rÚstageÚsubmit)rrÚkeyrFrLÚfr!r!r"Ú run_s3_job‰s    zFarOpt.run_s3_jobcCs4|jdd�x"|jD]}|d|kr|dSqWdS)aReturns UUID of a recipe based on friendly description/ recipe name :param description: friendly description/ recipe name :type description: string :return: First UUID that matches the description of the recipe :rtype: uuid4() F)Úverboser=r<N)Ú list_recipesÚrecipes)rr=Úrr!r!r"Úget_recipe_id_from_description¢s   z%FarOpt.get_recipe_id_from_descriptionc Csäy²t d¡|_|j |j¡|_|jjd|id�}|dd}|dd}t d¡t  d¡}t d d ƒ�}|  |||¡Wd QRXt j  d ¡|_d |_t d ¡d|_|j|d�Wn,tk rÞ}zt |¡dSd }~XYnXd S)zÃRuns already registered recipe :param recipe_id: UUID of recipe :type recipe_id: string :param micro: Submit as a micro job :type recipe_id: bool r;r<)ZKeyr@r,rzDownloading recipe...rLz/tmp/source.ziprMNz source.zipzConfigured job!T)rFF)r rArBrCr rDZget_itemrrrrNrOr(r,r0r1r2rrQrr) rZ recipe_idrFrr,rrLrSr r!r!r"Ú run_recipe°s$        zFarOpt.run_recipecCs6x&| ¡dkr&t| ¡ƒt d¡qWt d¡dS)zíPolls for the primary status of the container task that runs this job. You should see PROVISIONING > PENDING > RUNNING > STOPPED > JOB COMPLETED :return: primary status of the job that was submitted :rtype: list ÚSTOPPEDézJOB COMPLETED!N)Úprimary_statusr*ÚtimeÚsleeprr)rr!r!r"ÚwaitÐs z FarOpt.waitcCs¨t d¡t d¡}yxt ¡ d¡dttƒƒ}|  |j |j d|d¡}t dt|ƒ¡||_ d||_ t d|j ›d |j ›d�¡Wnt d ¡YnXd S) zaUploads the source.zip but does not submit to fargate. Useful when you want to run later z Staging jobrLz%Y-%m-%d-%H-%M-%Sú-zstaged/z /source.zipzStaged job! id: zLook for s3://z/staged/zCould not stage jobN)rrr rrÚnowÚstrftimerErÚ upload_filer1rrHrGr)rÚ s3_clientÚeventidrr!r!r"rPÝs    z FarOpt.stagec CsÌ|j�r¸t d¡t d¡}t d¡|_|j |j¡|_ |sâyvd|_ t   ¡  d¡dttƒƒ}| |j|j|d¡}t dt|ƒ¡||_|j|j|dd œ}|j j|d �Wn,tk rÞ}zt |¡dSd }~XYnXnÔy¤| ¡d |_ t d ¡t d¡}|j|jddt |j|jdœ¡ ¡d�}|j|j|jdd œ}|j j|d �|d}tt  !| ¡¡ "¡ƒt  !| ¡¡ "¡|_#Wn.tk �r´}zt |¡dSd }~XYnXn t d¡d |_$d S)a–Runs job defined in object params. Creates a new job ID to track and sets submitted to True. Check self.jobname to reference the job that was submitted. View self.logs() once the job has completed :param micro: Submit a micro job. By submitting a micro job, you are restricted to using ortools, pyomo and deap libraries for jobs that last up to 5 minutes :type micro: bool zSubmitting jobrLr;Fz%Y-%m-%d-%H-%M-%Sraz /source.zipzSubmitted job! id: )Újobidrr,)r@NTz{By submitting a micro job, you are restricted to using ortools, pyomo and deap libraries for jobs that last up to 5 minutesÚlambdaZRequestResponseZTail)Zs3bucketZs3key)Z FunctionNameZInvocationTypeZLogTypeZPayloadZ LogResultzPlease configure the job first!)%rrrr rrArBrCr rDrFrrbrcrErrdr1rrHrIrrrPZinvoker ÚjsonÚdumpsrrGÚencoder*Úbase64Ú b64decodeÚdecodeÚ micrologsr) rrFrerfrrJr Z lamclientZbase64_messager!r!r"rQðsR         z FarOpt.submité Tc CsÔt d¡}|j|j|d�}g}xª|dD]ž}| |dd|dd|dd|dd|d d|d dd œ¡|r(td |dd›d |dd›d|dd›d|dd›d|d d›� ƒq(W||_|S)a[Returns list of recipes registered :param limit: Number of recipes to return, Defaults to 10 :type limit: int, optional :param verbose: Verbose print of the recipe table, Defaults to True :type verbose: bool, optional :return: Recipe table scan (raw) results :rtype: boto3 response r;)Ú TableNameÚLimitÚItemsr<ÚSrr,r=r>r?)r<rr,r=r>r?z recipeid:z | bucket:z | path:z | description:z | maintainer:)r rÚscanr Úappendr*rW)rÚlimitrUÚ ddb_clientrZ allrecipesrJr!r!r"rV2s HRzFarOpt.list_recipesc Csšt d¡}|j|j|d�}g}xp|dD]d}| |dd|dd|dddœ¡|r(td |dd›d |dd›d |dd›�ƒq(W||_|S) aNReturns list of jobs submitted :param limit: Number of jobs to return, Defaults to 10 :type limit: int, optional :param verbose: Verbose print of the job table, Defaults to True :type verbose: bool, optional :return: job table scan (raw) results :rtype: boto3 response r;)rqrrrsrgrtrr,)rgrr,zjobid:z | bucket:z | path:)r rrur rvr*Újobs)rrwrUrxrZalljobsrJr!r!r"Ú list_jobsKs *6zFarOpt.list_jobsrc csÈd}d}xº|dkrÂ|dk r$d|i}ni}| ¡ddd d¡d}t d ¡}|jfd d ||d d œ|—Ž}|d}|d} t| ƒ}||kr | |d…} d}n ||}g} x| D] } | Vq²Wq WdS)z7Internal, use self.logs() instead of streaming NérZ nextTokenÚtasksÚtaskArnr%éÿÿÿÿÚlogsÚfaroptlogGroupzfaroptlogs/FarOptImage/T)Ú logGroupNameÚ logStreamNameZ startTimeZ startFromHeadZnextForwardTokenÚevents)ÚstatusÚsplitr rÚget_log_eventsÚlen) rÚ start_timeÚskipZ next_tokenZ event_countZ token_argÚtaskarnrrrƒÚevr!r!r"Ú stream_logsfs0     zFarOpt.stream_logscCsX|jrJ|jdkrJt d¡}| ¡ddd d¡d}|jd|d d �}n t d ¡d S) zStops a submitted task FÚecsr|rr}r%r~Ú FarOptClusterzUser stopped task)ÚclusterZtaskÚreasonz0Please ensure you have submitted a non-micro jobN) rrFr rr„r…Z stop_taskrr)rrrŠrr!r!r"ÚstopŒs  z FarOpt.stopcCs2x,|dD] }tt|dƒd|dƒq WdS)NrƒÚ timestampz | Úmessage)r*rE)rrr‹r!r!r"Ú printlogsžszFarOpt.printlogscCs|| ¡dkrh|jdkr\| ¡ddd d¡d}t d¡}|jd d |d �}| |¡qxt|j ƒntd | ¡ƒd S)z)Prints logs of a submitted job. )r[ZDEPROVISIONINGZRUNNINGFr|rr}r%r~rr€zfaroptlogs/FarOptImage/)r�r‚z,Please wait for the task to start running | N) r]rFr„r…r rr†r”r*ro)rrŠrrr!r!r"r£s      z FarOpt.logscCs&|jdkr| ¡dddSdSdS)z¸Returns the last status of the submitted job; Can be PROVISIONING > PENDING > RUNNING > STOPPED > JOB COMPLETED :return: primary staus :rtype: string Fr|rZ lastStatusr[N)rFr„)rr!r!r"r]·s zFarOpt.primary_statuscCsˆ|jrz|jdkrzt d¡}|jd|jd�}|d}|gkr^t d¡|jd|jdd�}|d}|jd|dd gd �}|St  d ¡d S) zxReturns the full status of the submitted job; used in primary_status, which should be enough for most use cases Fr�rŽ)r�Ú startedByZtaskArnsz-No running tasks. Checking completed tasks...r[)r�r•Z desiredStatusr)r�r|z$Please submit a non-micro job first!N) rrFr rZ list_tasksrHrrZdescribe_tasksr)rrZ response1Z running_tasksZ stopped_tasksrr!r!r"r„Âs"   z FarOpt.statuscCsPt d¡}| d|¡}|jd|jdœgt ¡tdd�t ¡ddd d gd �}|S) a"Returns raw metric data that was submitted from the backend. To use this, do from utils import * in your main.py, and then use log_metric like this, for e.g: log_metric('total_distance',total_distance) :return: response from cloudwatch :rtype: json string Ú cloudwatchrrg)ÚNameÚValueé)Úminutesr{ZAverageZMinimumZMaximum)Z DimensionsZ StartTimeZEndTimeZPeriodZ Statistics)r rAZMetricZget_statisticsrHrrbr)rZ metric_namer–Zmetricrr!r!r"Úget_metric_dataàs   zFarOpt.get_metric_dataN)rr )r:)F)F)F)rpT)rpT)rr)Ú__name__Ú __module__Ú __qualname__Ú__doc__r#r9rKrTrYrZr`rPrQrVrzrŒr‘r”rr]r„r›r!r!r!r"rs& 3 (    B   & r)r Zbotocore.exceptionsrrr(r&rrÚuuidrr^rlriÚ basicConfigÚINFOÚobjectrr!r!r!r"Ús