Merge lp:~frankban/charms/trusty/juju-gui/custom-port into lp:~juju-gui/charms/trusty/juju-gui/trunk

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 214
Proposed branch: lp:~frankban/charms/trusty/juju-gui/custom-port
Merge into: lp:~juju-gui/charms/trusty/juju-gui/trunk
Diff against target: 646 lines (+303/-30)
11 files modified
README.md (+5/-1)
config.yaml (+9/-1)
config/guiserver.conf.template (+3/-0)
hooks/backend.py (+6/-5)
hooks/utils.py (+43/-4)
server/guiserver/__init__.py (+1/-1)
server/guiserver/manage.py (+27/-3)
server/guiserver/tests/test_manage.py (+50/-6)
tests/20-functional.test (+11/-4)
tests/test_backends.py (+17/-4)
tests/test_utils.py (+131/-1)
To merge this branch: bzr merge lp:~frankban/charms/trusty/juju-gui/custom-port
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+241802@code.launchpad.net

Description of the change

Support user defined port.

Allow specifying a port on which the
GUI server starts listening for requests.

Tests: `make unittest`.

QA:
- bootstrap an environment;
- `make deploy`;
- wait for the unit to be deployed;
- visit the GUI URL, ensure port 80 redirects to 443 as usual
  and the GUI works as usual;
- switch to a customized port: `juju set juju-gui port=8080`;
- ensure the GUI is no longer reachable at port 80 or 443,
  instead it works correctly if you visit `https://<GUI-URL>:8080/`;
- switch to a another customized port: `juju set juju-gui port=443`;
- this time the GUI is still listening on default port for HTTPS,
  but port 80 no longer redirects to HTTPS;
- switch to an invalid port: `juju set juju-gui port=80000`;
- the GUI refuses to do that, and it falls back to using
  the default configuration: ensure port 80 redirects to 443;
- switch again to a customized valid port: `juju set juju-gui port=12345`;
- ensure the GUI server is listening on that port;
- finally, unset the port value: `juju unset juju-gui port`;
- back to defaults: ensure port 80 redirects to 443;
- destroy the environment.

Done, thank you.

https://codereview.appspot.com/174170043/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+241802_code.launchpad.net,

Message:
Please take a look.

Description:
Support user defined port.

Allow specifying a port on which the
GUI server starts listening for requests.

Tests: `make unittest`.

QA:
- bootstrap an environment;
- `make deploy`;
- wait for the unit to be deployed;
- visit the GUI URL, ensure port 80 redirects to 443 as usual
   and the GUI works as usual;
- switch to a customized port: `juju set juju-gui port=8080`;
- ensure the GUI is no longer reachable at port 80 or 443,
   instead it works correctly if you visit `https://<GUI-URL>:8080/`;
- switch to a another customized port: `juju set juju-gui port=443`;
- this time the GUI is still listening on default port for HTTPS,
   but port 80 no longer redirects to HTTPS;
- switch to an invalid port: `juju set juju-gui port=80000`;
- the GUI refuses to do that, and it falls back to using
   the default configuration: ensure port 80 redirects to 443;
- switch again to a customized valid port: `juju set juju-gui
port=12345`;
- ensure the GUI server is listening on that port;
- finally, unset the port value: `juju unset juju-gui port`;
- back to defaults: ensure port 80 redirects to 443;
- destroy the environment.

Done, thank you.

https://code.launchpad.net/~frankban/charms/trusty/juju-gui/custom-port/+merge/241802

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/174170043/

Affected files (+305, -30 lines):
   M README.md
   A [revision details]
   M config.yaml
   M config/guiserver.conf.template
   M hooks/backend.py
   M hooks/utils.py
   M server/guiserver/__init__.py
   M server/guiserver/manage.py
   M server/guiserver/tests/test_manage.py
   M tests/20-functional.test
   M tests/test_backends.py
   M tests/test_utils.py

Revision history for this message
Richard Harding (rharding) wrote :

LGTM no QA. Jeff and Francesco thanks! People are going to love getting
this feature into their hands.

https://codereview.appspot.com/174170043/

Revision history for this message
Jeff Pihach (hatch) wrote :

LGTM QA OK
This looks great and works great, thanks for taking over!

https://codereview.appspot.com/174170043/

Revision history for this message
Francesco Banconi (frankban) wrote :

*** Submitted:

Support user defined port.

Allow specifying a port on which the
GUI server starts listening for requests.

Tests: `make unittest`.

QA:
- bootstrap an environment;
- `make deploy`;
- wait for the unit to be deployed;
- visit the GUI URL, ensure port 80 redirects to 443 as usual
   and the GUI works as usual;
- switch to a customized port: `juju set juju-gui port=8080`;
- ensure the GUI is no longer reachable at port 80 or 443,
   instead it works correctly if you visit `https://<GUI-URL>:8080/`;
- switch to a another customized port: `juju set juju-gui port=443`;
- this time the GUI is still listening on default port for HTTPS,
   but port 80 no longer redirects to HTTPS;
- switch to an invalid port: `juju set juju-gui port=80000`;
- the GUI refuses to do that, and it falls back to using
   the default configuration: ensure port 80 redirects to 443;
- switch again to a customized valid port: `juju set juju-gui
port=12345`;
- ensure the GUI server is listening on that port;
- finally, unset the port value: `juju unset juju-gui port`;
- back to defaults: ensure port 80 redirects to 443;
- destroy the environment.

Done, thank you.

R=rharding, jeff.pihach
CC=
https://codereview.appspot.com/174170043

https://codereview.appspot.com/174170043/

Revision history for this message
Francesco Banconi (frankban) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.md'
2--- README.md 2014-04-22 09:27:01 +0000
3+++ README.md 2014-11-14 13:53:57 +0000
4@@ -191,6 +191,9 @@
5 7. It supports deploying local charms by proxying browser HTTPS connections to
6 the Juju HTTPS API backend. This also includes retrieving and listing local
7 charms' files.
8+8. By default, it listens on port 443 for HTTPS secure connections, and
9+ redirects port 80 requests to port 443. The port where the server is
10+ listening can be changed using the charm configuration "port" option.
11
12 ### Legacy server ###
13
14@@ -206,7 +209,8 @@
15 Use it only if the charm is deployed on a precise machine.
16 * The legacy server only provides features 1-4 from the list above. This means
17 bundle deployments, timed authentication tokens and local charms are not
18- available when using the legacy configuration.
19+ available when using the legacy configuration. Also changing the default port
20+ where the server is listening is not supported on the legacy server.
21
22 ## Contacting the Developers ##
23
24
25=== modified file 'config.yaml'
26--- config.yaml 2014-11-04 01:31:53 +0000
27+++ config.yaml 2014-11-14 13:53:57 +0000
28@@ -36,7 +36,8 @@
29 'https://github.com/mitechie/juju-gui.git @de5e61bf9fa') which will
30 clone the git repository and then check out the specified commit
31 before building the release.
32- - a URL: The release found at the given URL (ex: http://...) will be deployed.
33+ - a URL: The release found at the given URL (ex: http://...) will be
34+ deployed.
35 type: string
36 default: local
37 juju-gui-debug:
38@@ -49,6 +50,13 @@
39 Whether or not the console should be enabled for the browser.
40 type: boolean
41 default: false
42+ port:
43+ description: |
44+ Supply a different port to host the GUI on besides the default 80 and
45+ 443. If the provided port is not a valid TCP port (ranging from 1 to
46+ 65535) the defaults are used.
47+ type: int
48+ default:
49 command-log-file:
50 description: |
51 The log file where stdout and stderr should be sent for all commands
52
53=== modified file 'config/guiserver.conf.template'
54--- config/guiserver.conf.template 2014-08-29 15:17:01 +0000
55+++ config/guiserver.conf.template 2014-11-14 13:53:57 +0000
56@@ -21,6 +21,9 @@
57 --guiroot="{{gui_root}}" \
58 --sslpath="{{ssl_cert_path}}" \
59 --charmworldurl="{{charmworld_url}}" \
60+ {{if port}}
61+ --port={{port}} \
62+ {{endif}}
63 {{if sandbox}}
64 --sandbox \
65 {{else}}
66
67=== modified file 'hooks/backend.py'
68--- hooks/backend.py 2014-02-04 20:07:31 +0000
69+++ hooks/backend.py 2014-11-14 13:53:57 +0000
70@@ -44,7 +44,6 @@
71
72 from charmhelpers import (
73 log,
74- open_port,
75 )
76
77 import utils
78@@ -133,9 +132,10 @@
79 cached_fonts=config['cached-fonts'], ga_key=config['ga-key'],
80 show_get_juju_button=config['show-get-juju-button'],
81 password=config.get('password'))
82- # Expose the service.
83- open_port(80)
84- open_port(443)
85+ # Set up TCP ports.
86+ previous_port = backend.prev_config.get('port')
87+ current_port = backend.config.get('port')
88+ utils.setup_ports(previous_port, current_port)
89
90
91 class ServerInstallMixinBase(object):
92@@ -198,7 +198,8 @@
93 utils.start_builtin_server(
94 build_dir, config['ssl-cert-path'], config['serve-tests'],
95 config['sandbox'], config['builtin-server-logging'],
96- not config['secure'], config['charmworld-url'])
97+ not config['secure'], config['charmworld-url'],
98+ port=config.get('port'))
99
100 def stop(self, backend):
101 utils.stop_builtin_server()
102
103=== modified file 'hooks/utils.py'
104--- hooks/utils.py 2014-09-29 14:52:52 +0000
105+++ hooks/utils.py 2014-11-14 13:53:57 +0000
106@@ -46,6 +46,7 @@
107 'setup_apache_config',
108 'setup_gui',
109 'setup_haproxy_config',
110+ 'setup_ports',
111 'start_builtin_server',
112 'start_haproxy_apache',
113 'stop_builtin_server',
114@@ -71,8 +72,10 @@
115 import tempita
116
117 from charmhelpers import (
118+ close_port,
119 get_config,
120 log,
121+ open_port,
122 RESTART,
123 service_control,
124 STOP,
125@@ -413,6 +416,37 @@
126 render_to_file('config.js.template', context, config_js_path)
127
128
129+# Simple checker function for port ranges.
130+port_in_range = lambda port: 1 <= port <= 65535
131+
132+
133+def setup_ports(previous_port, current_port):
134+ """Open or close ports based on the supplied ports.
135+
136+ The given ports specify the previously provided and the current value.
137+ They can be int numbers if the ports are specified, None otherwise, in
138+ which case the default ones (80 and 443) are used.
139+ """
140+ # If a custom port was previously defined we want to make sure we close it.
141+ if previous_port is not None and port_in_range(previous_port):
142+ log('Closing user provided port {}.'.format(previous_port))
143+ close_port(previous_port)
144+ if current_port is not None:
145+ if port_in_range(current_port):
146+ # Ensure the default ports are closed when setting the custom one.
147+ log('Closing default ports 80 and 443.')
148+ close_port(80)
149+ close_port(443)
150+ # Open the custom defined port.
151+ log('Opening user provided port {}.'.format(current_port))
152+ open_port(current_port)
153+ return
154+ log('Ignoring provided port {}: not in range.'.format(current_port))
155+ log('Opening default ports 80 and 443.')
156+ open_port(80)
157+ open_port(443)
158+
159+
160 def setup_haproxy_config(ssl_cert_path, secure=True):
161 """Generate the haproxy configuration file."""
162 log('Setting up haproxy Upstart file.')
163@@ -513,7 +547,8 @@
164
165 def write_builtin_server_startup(
166 gui_root, ssl_cert_path, serve_tests=False, sandbox=False,
167- builtin_server_logging='info', insecure=False, charmworld_url=''):
168+ builtin_server_logging='info', insecure=False, charmworld_url='',
169+ port=None):
170 """Generate the builtin server Upstart file."""
171 log('Generating the builtin server Upstart file.')
172 context = {
173@@ -526,7 +561,8 @@
174 'charmworld_url': charmworld_url,
175 'http_proxy': os.environ.get('http_proxy'),
176 'https_proxy': os.environ.get('https_proxy'),
177- 'no_proxy': os.environ.get('no_proxy', os.environ.get('NO_PROXY'))
178+ 'no_proxy': os.environ.get('no_proxy', os.environ.get('NO_PROXY')),
179+ 'port': port,
180 }
181 if not sandbox:
182 api_url = 'wss://{}'.format(get_api_address())
183@@ -542,12 +578,15 @@
184
185 def start_builtin_server(
186 build_dir, ssl_cert_path, serve_tests, sandbox, builtin_server_logging,
187- insecure, charmworld_url):
188+ insecure, charmworld_url, port=None):
189 """Start the builtin server."""
190+ if (port is not None) and not port_in_range(port):
191+ # Do not use the user provided port if it is not valid.
192+ port = None
193 write_builtin_server_startup(
194 build_dir, ssl_cert_path, serve_tests=serve_tests, sandbox=sandbox,
195 builtin_server_logging=builtin_server_logging, insecure=insecure,
196- charmworld_url=charmworld_url)
197+ charmworld_url=charmworld_url, port=port)
198 log('Starting the builtin server.')
199 with su('root'):
200 service_control(BUILTIN_SERVER, RESTART)
201
202=== modified file 'server/guiserver/__init__.py'
203--- server/guiserver/__init__.py 2014-04-09 13:16:08 +0000
204+++ server/guiserver/__init__.py 2014-11-14 13:53:57 +0000
205@@ -37,7 +37,7 @@
206 which originally made the request.
207 """
208
209-VERSION = (0, 4, 0)
210+VERSION = (0, 4, 1)
211
212
213 def get_version():
214
215=== modified file 'server/guiserver/manage.py'
216--- server/guiserver/manage.py 2014-01-24 15:38:37 +0000
217+++ server/guiserver/manage.py 2014-11-14 13:53:57 +0000
218@@ -73,6 +73,18 @@
219 option_name, ', '.join(choices)))
220
221
222+def _validate_range(option_name, min_value, max_value):
223+ """Ensure the numeric value passed for the given option is in range.
224+
225+ The range is defined by min_value and max_value.
226+ Exit with an error if the value is not in the given range.
227+ """
228+ value = options[option_name]
229+ if (value is not None) and not (min_value <= value <= max_value):
230+ sys.exit('error: the {} argument must be included between '
231+ '{} and {}'.format(option_name, min_value, max_value))
232+
233+
234 def _get_ssl_options():
235 """Return a Tornado SSL options dict.
236
237@@ -120,11 +132,17 @@
238 define(
239 'charmworldurl', type=str,
240 help='The URL to use for Charmworld.')
241+ define(
242+ 'port', type=int,
243+ help='User defined port to run the server on. If no port is defined '
244+ 'the server will be started on 80 and 443 as per the default '
245+ 'port options from the charm.')
246
247 # In Tornado, parsing the options also sets up the default logger.
248 parse_command_line()
249 _validate_required('guiroot')
250 _validate_choices('apiversion', ('go', 'python'))
251+ _validate_range('port', 1, 65535)
252 _add_debug(logging.getLogger())
253 # Configure the asynchronous HTTP client used by proxy handlers.
254 AsyncHTTPClient.configure(
255@@ -133,13 +151,19 @@
256
257 def run():
258 """Run the server"""
259+ port = options.port
260 if options.insecure:
261 # Run the server over an insecure HTTP connection.
262- server().listen(80)
263+ if port is None:
264+ port = 80
265+ server().listen(port)
266 else:
267 # Default configuration: run the server over a secure HTTPS connection.
268- server().listen(443, ssl_options=_get_ssl_options())
269- redirector().listen(80)
270+ if port is None:
271+ port = 443
272+ redirector().listen(80)
273+ server().listen(port, ssl_options=_get_ssl_options())
274 version = guiserver.get_version()
275 logging.info('starting Juju GUI server v{}'.format(version))
276+ logging.info('listening on port {}'.format(port))
277 IOLoop.instance().start()
278
279=== modified file 'server/guiserver/tests/test_manage.py'
280--- server/guiserver/tests/test_manage.py 2013-08-08 09:20:37 +0000
281+++ server/guiserver/tests/test_manage.py 2014-11-14 13:53:57 +0000
282@@ -126,6 +126,31 @@
283 manage._validate_choices('arg1', self.choices)
284
285
286+class TestValidateRange(ValidatorTestMixin, unittest.TestCase):
287+
288+ value_range = (1, 10)
289+ error = 'error: the {} argument must be included between {} and {}'
290+
291+ def test_success(self):
292+ # The validation passes if the value is included in the range.
293+ for value in range(1, 11):
294+ with mock.patch('guiserver.manage.options', {'arg1': value}):
295+ manage._validate_range('arg1', *self.value_range)
296+
297+ def test_failure_invalid_range(self):
298+ # The validation fails if the value is not in range.
299+ error = self.error.format('arg1', *self.value_range)
300+ for value in (-50, 0, 11, 100):
301+ with mock.patch('guiserver.manage.options', {'arg1': value}):
302+ with self.assert_sysexit(error):
303+ manage._validate_range('arg1', *self.value_range)
304+
305+ def test_success_missing(self):
306+ # The validation succeeds if the value is missing.
307+ with mock.patch('guiserver.manage.options', {'arg1': None}):
308+ manage._validate_range('arg1', *self.value_range)
309+
310+
311 class TestGetSslOptions(unittest.TestCase):
312
313 def test_options(self):
314@@ -141,6 +166,11 @@
315
316 class TestRun(LogTrapTestCase, unittest.TestCase):
317
318+ expected_ssl_options = {
319+ 'certfile': '/my/sslpath/juju.crt',
320+ 'keyfile': '/my/sslpath/juju.key',
321+ }
322+
323 def mock_and_run(self, **kwargs):
324 """Run the application after mocking the IO loop and the options/apps.
325
326@@ -149,6 +179,7 @@
327 options = {
328 'apiversion': 'go',
329 'guiroot': '/my/guiroot',
330+ 'port': None,
331 'sslpath': '/my/sslpath',
332 }
333 options.update(kwargs)
334@@ -163,20 +194,33 @@
335 def test_secure_mode(self):
336 # The application is correctly run in secure mode.
337 _, redirector_listen, server_listen = self.mock_and_run(insecure=False)
338- expected_ssl_options = {
339- 'certfile': '/my/sslpath/juju.crt',
340- 'keyfile': '/my/sslpath/juju.key',
341- }
342 redirector_listen.assert_called_once_with(80)
343 server_listen.assert_called_once_with(
344- 443, ssl_options=expected_ssl_options)
345+ 443, ssl_options=self.expected_ssl_options)
346
347 def test_insecure_mode(self):
348 # The application is correctly run in insecure mode.
349 _, redirector_listen, server_listen = self.mock_and_run(insecure=True)
350- self.assertEqual(0, redirector_listen.call_count)
351+ self.assertFalse(redirector_listen.called)
352 server_listen.assert_called_once_with(80)
353
354+ def test_customized_port_secure_mode(self):
355+ # If the user provided a port, the server starts listening on that port
356+ # and the redirector is not used.
357+ _, redirector_listen, server_listen = self.mock_and_run(
358+ insecure=False, port=8080)
359+ self.assertFalse(redirector_listen.called)
360+ server_listen.assert_called_once_with(
361+ 8080, ssl_options=self.expected_ssl_options)
362+
363+ def test_customized_port_insecure_mode(self):
364+ # The application is correctly run in insecure mode with a user
365+ # provided port.
366+ _, redirector_listen, server_listen = self.mock_and_run(
367+ insecure=True, port=12345)
368+ self.assertFalse(redirector_listen.called)
369+ server_listen.assert_called_once_with(12345)
370+
371 def test_ioloop_started(self):
372 # The IO loop instance is started when the application is run.
373 ioloop_start, _, _ = self.mock_and_run()
374
375=== modified file 'tests/20-functional.test'
376--- tests/20-functional.test 2014-10-15 07:49:26 +0000
377+++ tests/20-functional.test 2014-11-14 13:53:57 +0000
378@@ -83,8 +83,6 @@
379
380 class DeployTestMixin(object):
381
382- port = '443'
383-
384 def setUp(self):
385 # Perform all graphical operations in memory.
386 vdisplay = Xvfb(width=1280, height=720)
387@@ -115,14 +113,14 @@
388 error='Browser warning dialog not found.')
389 continue_button.click()
390
391- def navigate_to(self, hostname, path='/'):
392+ def navigate_to(self, hostname, port=443, path='/'):
393 """Load a page using the current Selenium driver.
394
395 The page URL is calculated using the provided *hostname* and *path*.
396 Retry loading the page until the page is found or a timeout exception
397 is raised.
398 """
399- base_url = 'https://{}:{}'.format(hostname, self.port)
400+ base_url = 'https://{}:{}'.format(hostname, port)
401 url = urlparse.urljoin(base_url, path)
402
403 def page_ready(driver):
404@@ -238,6 +236,15 @@
405 self.assertIn('public', cache_directives)
406 self.assertIn('must-revalidate', cache_directives)
407
408+ def test_different_port(self):
409+ # The port on which the server listens can be successfully changed.
410+ options = {'port': 8080}
411+ self.service_name, unit_info = juju_deploy_gui(options=options)
412+ hostname = unit_info['public-address']
413+ self.navigate_to(hostname, port=8080)
414+ self.handle_browser_warning()
415+ self.assertEnvironmentIsConnected()
416+
417
418 class TestBuiltinServerLocalRelease(DeployTestMixin, unittest.TestCase):
419
420
421=== modified file 'tests/test_backends.py'
422--- tests/test_backends.py 2014-02-04 20:07:31 +0000
423+++ tests/test_backends.py 2014-11-14 13:53:57 +0000
424@@ -169,12 +169,12 @@
425 'install_missing_packages': mock.patch(
426 'backend.utils.install_missing_packages'),
427 'log': mock.patch('backend.log'),
428- 'open_port': mock.patch('backend.open_port'),
429 'parse_source': mock.patch(
430 'backend.utils.parse_source', mock_parse_source),
431 'save_or_create_certificates': mock.patch(
432 'backend.utils.save_or_create_certificates'),
433 'setup_gui': mock.patch('backend.utils.setup_gui'),
434+ 'setup_ports': mock.patch('backend.utils.setup_ports'),
435 'start_builtin_server': mock.patch(
436 'backend.utils.start_builtin_server'),
437 'start_haproxy_apache': mock.patch(
438@@ -280,7 +280,7 @@
439 mocks.compute_build_dir.assert_called_with(
440 config['juju-gui-debug'], config['serve-tests'])
441 self.assert_write_gui_config_called(mocks, config)
442- mocks.open_port.assert_has_calls([mock.call(80), mock.call(443)])
443+ mocks.setup_ports.assert_called_once_with(None, None)
444 mocks.start_haproxy_apache.assert_called_once_with(
445 mocks.compute_build_dir(), config['serve-tests'],
446 self.ssl_cert_path, config['secure'])
447@@ -295,14 +295,27 @@
448 mocks.compute_build_dir.assert_called_with(
449 config['juju-gui-debug'], config['serve-tests'])
450 self.assert_write_gui_config_called(mocks, config)
451- mocks.open_port.assert_has_calls([mock.call(80), mock.call(443)])
452+ mocks.setup_ports.assert_called_once_with(None, None)
453 mocks.start_builtin_server.assert_called_once_with(
454 mocks.compute_build_dir(), self.ssl_cert_path,
455 config['serve-tests'], config['sandbox'],
456 config['builtin-server-logging'], not config['secure'],
457- config['charmworld-url'])
458+ config['charmworld-url'], port=None)
459 self.assertFalse(mocks.start_haproxy_apache.called)
460
461+ def test_start_go_builtin_user_provided_port(self):
462+ # Start a juju backend with builtin server and a user provided port.
463+ config = self.make_config({'builtin-server': True, 'port': 8080})
464+ test_backend = backend.Backend(config=config)
465+ with self.mock_all() as mocks:
466+ test_backend.start()
467+ mocks.setup_ports.assert_called_once_with(None, 8080)
468+ mocks.start_builtin_server.assert_called_once_with(
469+ mocks.compute_build_dir(), self.ssl_cert_path,
470+ config['serve-tests'], config['sandbox'],
471+ config['builtin-server-logging'], not config['secure'],
472+ config['charmworld-url'], port=8080)
473+
474 def test_stop_go_legacy(self):
475 # Stop a juju-core backend with legacy server.
476 config = self.make_config({'builtin-server': False})
477
478=== modified file 'tests/test_utils.py'
479--- tests/test_utils.py 2014-02-04 20:07:31 +0000
480+++ tests/test_utils.py 2014-11-14 13:53:57 +0000
481@@ -48,12 +48,14 @@
482 install_missing_packages,
483 log_hook,
484 parse_source,
485+ port_in_range,
486 remove_apache_setup,
487 remove_haproxy_setup,
488 render_to_file,
489 save_or_create_certificates,
490 setup_apache_config,
491 setup_haproxy_config,
492+ setup_ports,
493 start_builtin_server,
494 start_haproxy_apache,
495 stop_builtin_server,
496@@ -908,6 +910,16 @@
497 self.assertNotIn('--sandbox', guiserver_conf)
498 self.assertIn('--charmworldurl="http://charmworld.example.com/"',
499 guiserver_conf)
500+ # By default the port is not provided to the GUI server.
501+ self.assertNotIn('--port', guiserver_conf)
502+
503+ def test_write_builtin_server_startup_with_port(self):
504+ # The builtin server Upstart file is properly generate when a
505+ # customized port is provided.
506+ write_builtin_server_startup(
507+ JUJU_GUI_DIR, self.ssl_cert_path, port=8000)
508+ guiserver_conf = self.files['guiserver.conf']
509+ self.assertIn('--port=8000', guiserver_conf)
510
511 def test_write_builtin_server_startup_sandbox_and_logging(self):
512 # The upstart configuration file for the GUI server is correctly
513@@ -927,7 +939,7 @@
514 start_builtin_server(
515 JUJU_GUI_DIR, self.ssl_cert_path, serve_tests=False, sandbox=False,
516 builtin_server_logging='info', insecure=False,
517- charmworld_url='http://charmworld.example.com/')
518+ charmworld_url='http://charmworld.example.com/', port=443)
519 self.assertEqual(self.svc_ctl_call_count, 1)
520 self.assertEqual(self.service_names, ['guiserver'])
521 self.assertEqual(self.actions, [charmhelpers.RESTART])
522@@ -1040,6 +1052,124 @@
523 self.assertIn('cachedFonts: true', js_conf)
524
525
526+class TestPortInRange(unittest.TestCase):
527+
528+ def test_valid_port(self):
529+ # True is returned if the port is in range.
530+ for port in (1, 80, 443, 1234, 8080, 54321, 65535):
531+ self.assertTrue(port_in_range(port), port)
532+
533+ def test_invalid_port(self):
534+ # False is returned if the port is not in range.
535+ for port in (-10, 0, 65536, 100000):
536+ self.assertFalse(port_in_range(port), port)
537+
538+
539+@mock.patch('utils.close_port')
540+@mock.patch('utils.open_port')
541+@mock.patch('utils.log')
542+class TestSetupPorts(unittest.TestCase):
543+
544+ def test_default_ports(self, mock_log, mock_open_port, mock_close_port):
545+ # The default ports are properly opened.
546+ setup_ports(None, None)
547+ mock_log.assert_called_once_with('Opening default ports 80 and 443.')
548+ self.assertEqual(2, mock_open_port.call_count)
549+ mock_open_port.assert_has_calls([mock.call(80), mock.call(443)])
550+ self.assertFalse(mock_close_port.called)
551+
552+ def test_from_defaults_to_new_port(
553+ self, mock_log, mock_open_port, mock_close_port):
554+ # The user provides a customized port.
555+ setup_ports(None, 8080)
556+ self.assertEqual(2, mock_log.call_count)
557+ mock_log.assert_has_calls([
558+ mock.call('Closing default ports 80 and 443.'),
559+ mock.call('Opening user provided port 8080.'),
560+ ])
561+ mock_open_port.assert_called_once_with(8080)
562+ self.assertEqual(2, mock_close_port.call_count)
563+ mock_close_port.assert_has_calls([mock.call(80), mock.call(443)])
564+
565+ def test_from_previous_port_to_new_port(
566+ self, mock_log, mock_open_port, mock_close_port):
567+ # The user switches from a previously provided port to a new one.
568+ setup_ports(8080, 1234)
569+ self.assertEqual(3, mock_log.call_count)
570+ mock_log.assert_has_calls([
571+ mock.call('Closing user provided port 8080.'),
572+ # Always close the default ports in those cases.
573+ mock.call('Closing default ports 80 and 443.'),
574+ mock.call('Opening user provided port 1234.')
575+ ])
576+ mock_open_port.assert_called_once_with(1234)
577+ self.assertEqual(3, mock_close_port.call_count)
578+ mock_close_port.assert_has_calls([
579+ mock.call(8080), mock.call(80), mock.call(443)])
580+
581+ def test_from_previous_port_to_defaults(
582+ self, mock_log, mock_open_port, mock_close_port):
583+ # The user provided a port and then switches back to defaults.
584+ setup_ports(1234, None)
585+ self.assertEqual(2, mock_log.call_count)
586+ mock_log.assert_has_calls([
587+ mock.call('Closing user provided port 1234.'),
588+ mock.call('Opening default ports 80 and 443.'),
589+ ])
590+ self.assertEqual(2, mock_open_port.call_count)
591+ mock_open_port.assert_has_calls([mock.call(80), mock.call(443)])
592+ mock_close_port.assert_called_once_with(1234)
593+
594+ def test_from_previous_port_to_invalid(
595+ self, mock_log, mock_open_port, mock_close_port):
596+ # The user switches from a previously provided port to an invalid one.
597+ setup_ports(8080, 0)
598+ self.assertEqual(3, mock_log.call_count)
599+ mock_log.assert_has_calls([
600+ mock.call('Closing user provided port 8080.'),
601+ mock.call('Ignoring provided port 0: not in range.'),
602+ mock.call('Opening default ports 80 and 443.'),
603+ ])
604+ self.assertEqual(2, mock_open_port.call_count)
605+ mock_open_port.assert_has_calls([mock.call(80), mock.call(443)])
606+ mock_close_port.assert_called_once_with(8080)
607+
608+ def test_from_defaults_to_invalid(
609+ self, mock_log, mock_open_port, mock_close_port):
610+ # The user provides an invalid port.
611+ setup_ports(None, 100000)
612+ self.assertEqual(2, mock_log.call_count)
613+ mock_log.assert_has_calls([
614+ mock.call('Ignoring provided port 100000: not in range.'),
615+ mock.call('Opening default ports 80 and 443.'),
616+ ])
617+ self.assertEqual(2, mock_open_port.call_count)
618+ mock_open_port.assert_has_calls([mock.call(80), mock.call(443)])
619+ self.assertFalse(mock_close_port.called)
620+
621+ def test_from_invalid_to_new_port(
622+ self, mock_log, mock_open_port, mock_close_port):
623+ # The user fixes a previously provided invalid port.
624+ setup_ports(123456, 8000)
625+ self.assertEqual(2, mock_log.call_count)
626+ mock_log.assert_has_calls([
627+ mock.call('Closing default ports 80 and 443.'),
628+ mock.call('Opening user provided port 8000.')
629+ ])
630+ mock_open_port.assert_called_once_with(8000)
631+ self.assertEqual(2, mock_close_port.call_count)
632+ mock_close_port.assert_has_calls([mock.call(80), mock.call(443)])
633+
634+ def test_from_invalid_to_defaults(
635+ self, mock_log, mock_open_port, mock_close_port):
636+ # The user switches back to default after providing an invalid port.
637+ setup_ports(0, None)
638+ mock_log.assert_called_once_with('Opening default ports 80 and 443.')
639+ self.assertEqual(2, mock_open_port.call_count)
640+ mock_open_port.assert_has_calls([mock.call(80), mock.call(443)])
641+ self.assertFalse(mock_close_port.called)
642+
643+
644 @mock.patch('utils.run')
645 @mock.patch('utils.log')
646 @mock.patch('utils.cmd_log', mock.Mock())

Subscribers

People subscribed via source and target branches