@@ -20,54 +20,70 @@ class OptimizerTcpManager:
2020 """
2121
2222 def __init__ (self , optimizer_path = None , ip = None , port = None ):
23- """Constructs instance of <code>OptimizerTcpManager</code>
23+ """
24+ Constructs instance of <code>OptimizerTcpManager</code>
25+
26+ There are three ways to use this constructor:
27+
28+ - OptimizerTcpManager(optimizer_path): creates a TCP manager for a local
29+ TCP server using the default IP and port of that TCP server (specified
30+ upon code generation)
31+ - OptimizerTcpManager(optimizer_path, ip, port): creates a TCP manager
32+ for a local TCP server, but overrides the default IP and port. This way
33+ the user can set the address '0.0.0.0', so that the TCP server binds on
34+ all IPs, or '127.0.0.1' so that it is accessible only locally, or a VPN
35+ IP address, so that the optimizer is accessible only over a private
36+ network.
37+ - OptimizerTcpManager(ip, port): If a path is not provided, then the
38+ TCP manager can be used to connect to a remote TCP server, as a client,
39+ but cannot be used to start the server.
2440
2541 Args:
26- optimizer_path: path to auto-generated optimizer (just to
27- be clear: this is the folder that contains <code>optimizer.yml</code>)
42+ :param optimizer_path:
43+ path to auto-generated optimizer (just to be clear: this is
44+ the folder that contains <code>optimizer.yml</code>)
45+
46+ :param ip:
47+ the user can provide the IP of a remote TCP server (must be up and
48+ running) so as to establish a remote connection. In that case `path`
49+ must be equal to `None` (see examples above)
50+
51+ :param port: see ip
2852
2953 Returns:
3054 New instance of <code>OptimizerTcpManager</code>
3155 """
3256 self .__optimizer_path = optimizer_path
3357 if optimizer_path is not None :
34- self .__optimizer_details_from_yml = None
58+ self .__optimizer_details = None # create attribute (including IP and port)
3559 self .__load_tcp_details ()
60+ if ip is not None :
61+ self .__optimizer_details ['tcp' ]['ip' ] = ip
62+ if port is not None :
63+ self .__optimizer_details ['tcp' ]['port' ] = port
3664 elif ip is not None and port is not None :
37- self .__optimizer_details_from_yml = {"tcp" : {"ip" : ip , "port" : port }}
65+ self .__optimizer_details = {"tcp" : {"ip" : ip , "port" : port }}
3866 else :
3967 raise Exception ("Illegal arguments" )
4068 # Check whether the optimizer was built with the current version of opengen
41- opengen_version = self .__optimizer_details_from_yml ['build' ]['opengen_version' ]
69+ opengen_version = self .__optimizer_details ['build' ]['opengen_version' ]
4270 current_opengen_version = pkg_resources .require ("opengen" )[0 ].version
4371 if current_opengen_version != opengen_version :
4472 logging .warn ('the target optimizer was build with a different version of opengen (%s)' % opengen_version )
4573 logging .warn ('you are running opengen version %s' % current_opengen_version )
4674
75+ logging .info ("TCP/IP details: %s:%d" ,
76+ self .__optimizer_details ['tcp' ]['ip' ],
77+ self .__optimizer_details ['tcp' ]['port' ])
78+
4779 def __load_tcp_details (self ):
48- logging .info ("loading TCP/IP details" )
4980 yaml_file = os .path .join (self .__optimizer_path , "optimizer.yml" )
5081 with open (yaml_file , 'r' ) as stream :
51- self .__optimizer_details_from_yml = yaml .safe_load (stream )
52- details = self .__optimizer_details_from_yml
53- logging .info ("TCP/IP details: %s:%d" , details ['tcp' ]['ip' ], details ['tcp' ]['port' ])
54-
55- def __threaded_start (self ):
56- optimizer_details = self .__optimizer_details_from_yml
57- logging .info ("Starting TCP/IP server at %s:%d (in a detached thread)" ,
58- optimizer_details ['tcp' ]['ip' ],
59- optimizer_details ['tcp' ]['port' ])
60- command = ['cargo' , 'run' , '-q' ]
61- if optimizer_details ['build' ]['build_mode' ] == 'release' :
62- command .append ('--release' )
63- tcp_dir_name = "tcp_iface_" + optimizer_details ['meta' ]['optimizer_name' ]
64- tcp_iface_directory = os .path .join (self .__optimizer_path , tcp_dir_name )
65- p = subprocess .Popen (command , cwd = tcp_iface_directory )
66- p .wait ()
82+ self .__optimizer_details = yaml .safe_load (stream )
6783
6884 @retry (tries = 10 , delay = 1 )
6985 def __obtain_socket_connection (self ):
70- tcp_data = self .__optimizer_details_from_yml
86+ tcp_data = self .__optimizer_details
7187 ip = tcp_data ['tcp' ]['ip' ]
7288 port = tcp_data ['tcp' ]['port' ]
7389 s = socket .socket (socket .AF_INET , socket .SOCK_STREAM , 0 )
@@ -101,31 +117,62 @@ def ping(self):
101117 return json .loads (data )
102118
103119 def __check_if_server_is_running (self ):
104- tcp_data = self .__optimizer_details_from_yml
120+ tcp_data = self .__optimizer_details
105121 ip = tcp_data ['tcp' ]['ip' ]
106122 port = tcp_data ['tcp' ]['port' ]
107123 s = socket .socket (socket .AF_INET , socket .SOCK_STREAM , 0 )
108124 return 0 == s .connect_ex ((ip , port ))
109125
126+ @property
127+ def details (self ):
128+ return self .__optimizer_details
129+
110130 def start (self ):
111- """Starts the TCP server"""
112- # start the server in a separate thread
131+ """Starts the TCP server
132+
133+ Note: this method starts a *local* server whose path must have been
134+ provided - we cannot start a remote server.
135+
136+ The server starts on a separate thread, so this method does not block
137+ the execution of the caller's programme.
113138
139+ """
140+
141+ # Check if a path has been provided; if not,
114142 if self .__optimizer_path is None :
115143 raise Exception ("No optimizer path provided - cannot start a remote server" )
116144
145+ # Server start data
146+ tcp_data = self .__optimizer_details
147+ ip = tcp_data ['tcp' ]['ip' ]
148+ port = tcp_data ['tcp' ]['port' ]
149+
150+ # Check if any of the ip/port pairs is occupied
117151 if self .__check_if_server_is_running ():
118- msg = "Port %d not available" % self . __optimizer_details_from_yml [ 'tcp' ][ ' port' ]
152+ msg = "Port %d not available" % port
119153 raise Exception (msg )
120154
155+ def threaded_start ():
156+ optimizer_details = self .__optimizer_details
157+ logging .info ("Starting TCP/IP server at %s:%d (in a detached thread)" ,
158+ ip , port )
159+ command = ['cargo' , 'run' , '-q' , '--' , '--port=%d' % port , '--ip=%s' % ip ]
160+ if optimizer_details ['build' ]['build_mode' ] == 'release' :
161+ command .append ('--release' )
162+ tcp_dir_name = "tcp_iface_" + optimizer_details ['meta' ]['optimizer_name' ]
163+ tcp_iface_directory = os .path .join (self .__optimizer_path , tcp_dir_name )
164+ p = subprocess .Popen (command , cwd = tcp_iface_directory )
165+ p .wait ()
166+
167+ # start the server in a separate thread
121168 logging .info ("Starting TCP/IP server thread" )
122- thread = Thread (target = self . __threaded_start )
169+ thread = Thread (target = threaded_start )
123170 thread .start ()
124171
125172 # ping the server until it responds so that we know it's
126173 # up and running
127174 logging .info ("Waiting for server to start" )
128- time .sleep (2 )
175+ time .sleep (0.1 )
129176 self .ping ()
130177
131178 def kill (self ):
0 commit comments