1+ from cloudshell .sandbox_rest .sandbox_api import SandboxRestApiSession , InputParam
2+ from cloudshell .sandbox_rest .helpers .polling_helpers import poll_sandbox_setup , poll_sandbox_teardown , \
3+ poll_execution_for_completion , SandboxStates , SANDBOX_SETUP_STATES , SANDBOX_ACTIVE_STATES
4+ from cloudshell .sandbox_rest .sandbox_components import SandboxRestComponents
15import json
2- import logging
36from typing import List
4-
5- from cloudshell .sandbox_rest .sandbox_api import SandboxRestApiSession , InputParam
6- from cloudshell .sandbox_rest .helpers import polling_helpers as poll_help
7+ from timeit import default_timer
78
89
910class SandboxSetupError (Exception ):
@@ -20,70 +21,145 @@ class CommandFailedException(Exception):
2021 pass
2122
2223
23- # TODO:
24- # add context manager methods
25- # review design of class
26- # add logger
27- # publish env vars
28-
29-
3024class SandboxRestController :
31- def __init__ (self , api : SandboxRestApiSession , sandbox_id = "" , log_file_handler = False ):
25+ def __init__ (self , api : SandboxRestApiSession , sandbox_id = "" , disable_prints = False ):
3226 self .api = api
3327 self .sandbox_id = sandbox_id
34- self .logger = None
28+ self .components = SandboxRestComponents ()
29+ self .setup_finished = False
30+ self .sandbox_ended = False
31+ self .setup_errors : List [dict ] = None
32+ self .teardown_errors : List [dict ] = None
33+ self .disable_prints = disable_prints
34+
35+ # when passing in existing sandbox id update instance state, otherwise let "start sandbox" handle it
36+ if self .sandbox_id :
37+ self ._handle_sandbox_id_on_init ()
3538
3639 def __enter__ (self ):
3740 return self
3841
3942 def __exit__ (self , exc_type , exc_val , exc_tb ):
40- pass
43+ if exc_type :
44+ err_msg = f"Exiting sandbox scope with error. f{ exc_type } : { exc_val } "
45+ self ._print (err_msg )
46+
47+ def _handle_sandbox_id_on_init (self ):
48+ self .update_components ()
49+ self .update_state_flags ()
50+
51+ def update_components (self ):
52+ components = self .api .get_sandbox_components (self .sandbox_id )
53+ self .components .refresh_components (components )
54+
55+ def update_state_flags (self ):
56+ details = self .api .get_sandbox_details (self .sandbox_id )
57+ sandbox_state = details ["state" ]
58+ if sandbox_state not in SANDBOX_SETUP_STATES :
59+ self .setup_finished = True
60+ elif sandbox_state not in SANDBOX_ACTIVE_STATES :
61+ self .sandbox_ended = True
62+
63+ def _print (self , message ):
64+ if not self .disable_prints :
65+ print (message )
4166
4267 def start_sandbox_and_poll (self , blueprint_id : str , sandbox_name = "" , duration = "PT1H30M" ,
4368 bp_params : List [InputParam ] = None , permitted_users : List [str ] = None ,
44- max_polling_minutes = 30 ) -> dict :
69+ max_polling_minutes = 30 , raise_setup_exception = True ) -> dict :
4570 """ Start sandbox, poll for result, get back sandbox info """
46- start_response = self .api .start_sandbox (blueprint_id , sandbox_name , duration , bp_params , permitted_users )
47- sb_id = start_response ["id" ]
48- sandbox_details = poll_help .poll_sandbox_setup (self .api , sb_id , max_polling_minutes ,
49- polling_frequency_seconds = 30 )
71+ if self .sandbox_id :
72+ raise ValueError (f"Sandbox already has id '{ self .sandbox_id } '.\n "
73+ f"Start blueprint with new sandbox controller instance" )
5074
75+ self ._print (f"Starting blueprint { blueprint_id } " )
76+ start = default_timer ()
77+ start_response = self .api .start_sandbox (blueprint_id , sandbox_name , duration , bp_params , permitted_users )
78+ self .sandbox_id = start_response ["id" ]
79+ sandbox_details = poll_sandbox_setup (self .api , self .sandbox_id , max_polling_minutes ,
80+ polling_frequency_seconds = 30 )
81+ self .update_components ()
5182 sandbox_state = sandbox_details ["state" ]
5283 stage = sandbox_details ["setup_stage" ]
5384 name = sandbox_details ["name" ]
5485
55- if "error" in sandbox_state . lower () :
56- activity_errors = self . _get_all_activity_errors ( sb_id )
86+ if sandbox_state == SandboxStates . ready_state . value :
87+ self . setup_finished = True
5788
58- # TODO: print this? log it? or dump into exception message?
89+ # scan activity feed for errors
90+ if sandbox_state == SandboxStates .error_state .value :
91+ self .setup_finished = True
92+ activity_errors = self ._get_all_activity_errors (self .sandbox_id )
5993 if activity_errors :
60- print (f"=== Activity Feed Errors ===\n { json .dumps (activity_errors , indent = 4 )} " )
61-
62- err_msg = (f"Sandbox '{ name } ' Error during SETUP.\n "
63- f"Stage: '{ stage } '. State '{ sandbox_state } '. Sandbox ID: '{ sb_id } '" )
64- raise SandboxSetupError (err_msg )
94+ # print and store setup error data
95+ self .setup_errors = activity_errors
96+ err_msg = f"Error Events during setup:\n { json .dumps (activity_errors , indent = 4 )} "
97+ self ._print (err_msg )
98+
99+ if raise_setup_exception :
100+ err_msg = (f"Sandbox '{ name } ' Error during SETUP. See events for details.\n "
101+ f"Stage: '{ stage } '. State '{ sandbox_state } '. Sandbox ID: '{ self .sandbox_id } '" )
102+ raise SandboxSetupError (err_msg )
103+
104+ total_minutes = (default_timer () - start ) / 60
105+ self ._print (f"Setup finished after { total_minutes :.2f} minutes." )
65106 return sandbox_details
66107
67- def stop_sandbox_and_poll (self , sandbox_id : str , max_polling_minutes = 30 ) -> dict :
108+ def stop_sandbox_and_poll (self , sandbox_id : str , max_polling_minutes = 30 , raise_teardown_exception = True ) -> dict :
109+ if self .sandbox_ended :
110+ raise ValueError (f"sandbox { self .sandbox_id } already in completed state" )
111+
112+ last_activity_id = self .api .get_sandbox_activity (self .sandbox_id , tail = 1 )["events" ]["id" ]
113+ start = default_timer ()
68114 self .api .stop_sandbox (sandbox_id )
69- sandbox_details = poll_help .poll_sandbox_teardown (self .api , sandbox_id , max_polling_minutes ,
70- polling_frequency_seconds = 30 )
115+ sandbox_details = poll_sandbox_teardown (self .api , sandbox_id , max_polling_minutes ,
116+ polling_frequency_seconds = 30 )
117+ self .update_components ()
71118 sandbox_state = sandbox_details ["state" ]
72119 stage = sandbox_details ["setup_stage" ]
73120 name = sandbox_details ["name" ]
74121
75- if "error" in sandbox_state .lower ():
76- tail_error_count = 5
77- tailed_errors = self .api .get_sandbox_activity (sandbox_id , error_only = True , tail = tail_error_count )["events" ]
78- print (f"=== Last { tail_error_count } Errors ===\n { json .dumps (tailed_errors , indent = 4 )} " )
79- err_msg = (f"Sandbox '{ name } ' Error during SETUP.\n "
80- f"Stage: '{ stage } '. State '{ sandbox_state } '. Sandbox ID: '{ sandbox_id } '" )
81- raise SandboxTeardownError (err_msg )
122+ if sandbox_state == SandboxStates .ended_state .value :
123+ self .sandbox_ended = True
124+
125+ tail_error_count = 5
126+ tailed_errors = self .api .get_sandbox_activity (sandbox_id ,
127+ error_only = True ,
128+ from_event_id = last_activity_id + 1 ,
129+ tail = tail_error_count )["events" ]
130+ if tailed_errors :
131+ self .sandbox_ended = True
132+ self .teardown_errors = tailed_errors
133+ self ._print (f"=== Last { tail_error_count } Errors ===\n { json .dumps (tailed_errors , indent = 4 )} " )
134+
135+ if raise_teardown_exception :
136+ err_msg = (f"Sandbox '{ name } ' Error during SETUP.\n "
137+ f"Stage: '{ stage } '. State '{ sandbox_state } '. Sandbox ID: '{ sandbox_id } '" )
138+ raise SandboxTeardownError (err_msg )
139+
140+ total_minutes = (default_timer () - start ) / 60
141+ self ._print (f"Teardown done in { total_minutes :.2f} minutes." )
82142 return sandbox_details
83143
144+ def rerun_setup (self ):
145+ pass
146+
147+ def run_sandbox_command_and_poll (self ):
148+ pass
149+
150+ def run_component_command_and_poll (self ):
151+ pass
152+
84153 def _get_all_activity_errors (self , sandbox_id ):
85- activity_results = self .api .get_sandbox_activity (sandbox_id , error_only = True )
86- return activity_results ["events" ]
154+ return self .api .get_sandbox_activity (sandbox_id , error_only = True )["events" ]
87155
88156 def publish_sandbox_id_to_env_vars (self ):
157+ """ publish sandbox id as environment variable for different CI process to pick up """
89158 pass
159+
160+
161+ if __name__ == "__main__" :
162+ api = SandboxRestApiSession ("localhost" , "admin" , "admin" )
163+ controller = SandboxRestController (api )
164+ response = controller .start_sandbox_and_poll ("rest test" , "lolol" )
165+ print (json .dumps (response , indent = 4 ))
0 commit comments