Merge lp:~fginther/juju-deployer/juju-2-minimal into lp:juju-deployer

Proposed by Francis Ginther
Status: Merged
Merged at revision: 169
Proposed branch: lp:~fginther/juju-deployer/juju-2-minimal
Merge into: lp:juju-deployer
Diff against target: 264 lines (+98/-25)
6 files modified
deployer/cli.py (+14/-2)
deployer/env/base.py (+21/-5)
deployer/env/go.py (+8/-1)
deployer/env/watchers.py (+14/-2)
deployer/tests/test_goenv.py (+6/-1)
deployer/utils.py (+35/-14)
To merge this branch: bzr merge lp:~fginther/juju-deployer/juju-2-minimal
Reviewer Review Type Date Requested Status
Tim Van Steenburgh (community) Approve
Review via email: mp+290722@code.launchpad.net

Commit message

Add basic support for juju 2 controllers.

Description of the change

Add basic support for juju 2 controllers.

Adds the ability to deploy to Juju 2 models. Does not support bootstrapping of a juju 2 cloud. Also, deploying to models other then the controller's admin model will run into lp:1560201 until it is resolved.

To post a comment you must log in.
Revision history for this message
Tim Van Steenburgh (tvansteenburgh) wrote :

LGTM!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'deployer/cli.py'
2--- deployer/cli.py 2015-09-23 15:32:46 +0000
3+++ deployer/cli.py 2016-04-01 12:01:16 +0000
4@@ -17,7 +17,8 @@
5 from deployer.config import ConfigStack
6 from deployer.env import select_runtime
7 from deployer.action import diff, importer
8-from deployer.utils import ErrorExit, setup_logging, get_env_name
9+from deployer.utils import (
10+ ErrorExit, setup_logging, get_env_name, get_juju_major_version)
11
12
13 def setup_parser():
14@@ -83,7 +84,9 @@
15 dest="deploy_delay", default=0)
16 parser.add_argument(
17 '-e', '--environment', action='store', dest='juju_env',
18- help='Deploy to a specific Juju environment.',
19+ help=('Deploy to a specific Juju environment or model. '
20+ 'For Juju 2 models, must be specified with the '
21+ '<controller>:<model> notation.'),
22 default=os.getenv('JUJU_ENV'))
23 parser.add_argument(
24 '-o', '--override', action='append', type=str,
25@@ -165,6 +168,15 @@
26 sys.exit(1)
27 log.debug('Using runtime %s on %s', env.__class__.__name__, env_name)
28
29+ if get_juju_major_version() == 2:
30+ if len(env_name.split(':')) != 2:
31+ log.error("Juju 2 environment names must be in "
32+ "<controller>:<model> format.")
33+ sys.exit(1)
34+ if options.bootstrap:
35+ log.error("Bootstrap of a Juju 2 cloud is not supported.")
36+ sys.exit(1)
37+
38 config = ConfigStack(options.configs or [], options.series)
39
40 # Destroy services and exit
41
42=== modified file 'deployer/env/base.py'
43--- deployer/env/base.py 2015-09-23 15:32:46 +0000
44+++ deployer/env/base.py 2016-04-01 12:01:16 +0000
45@@ -2,7 +2,7 @@
46
47 from ..utils import (
48 _get_juju_home, path_join, yaml_load, ErrorExit,
49- yaml_dump, temp_file, _check_call)
50+ yaml_dump, temp_file, _check_call, get_juju_major_version)
51
52
53 class BaseEnvironment(object):
54@@ -21,8 +21,12 @@
55 return _check_call(*args, **kwargs)
56
57 def _named_env(self, params):
58+ if get_juju_major_version() == 1:
59+ flag = "-e"
60+ else:
61+ flag = "-m"
62 if self.name:
63- params.extend(["-e", self.name])
64+ params.extend([flag, self.name])
65 return params
66
67 def _get_env_config(self):
68@@ -51,16 +55,24 @@
69 # fh.write(yaml_dump(config))
70 # fh.flush()
71
72+ def _get_agent_state(self, entity):
73+ try:
74+ return entity['agent-state']
75+ except KeyError:
76+ # 'agent-state' has been removed for new versions of Juju. Respond
77+ # with the closest status parameter.
78+ return entity['jujustatus']['Current']
79+
80 def _get_units_in_error(self, status=None):
81 units = []
82 if status is None:
83 status = self.status()
84 for s in status.get('services', {}).keys():
85 for uid, u in status['services'][s].get('units', {}).items():
86- if 'error' in u['agent-state']:
87+ if 'error' in self._get_agent_state(u):
88 units.append(uid)
89 for uid, u in u.get('subordinates', {}).items():
90- if 'error' in u['agent-state']:
91+ if 'error' in self._get_agent_state(u):
92 units.append(uid)
93 return units
94
95@@ -124,7 +136,11 @@
96 (mid.isdigit() and int(mid) == 0)):
97 # Don't kill state server
98 raise RuntimeError("Can't terminate machine 0")
99- params = self._named_env(["juju", "terminate-machine"])
100+ if get_juju_major_version() == 1:
101+ terminate_action = "terminate-machine"
102+ else:
103+ terminate_action = "remove-machine"
104+ params = self._named_env(["juju", terminate_action])
105 params.append(mid)
106 if force:
107 params.append('--force')
108
109=== modified file 'deployer/env/go.py'
110--- deployer/env/go.py 2015-09-23 15:32:46 +0000
111+++ deployer/env/go.py 2016-04-01 12:01:16 +0000
112@@ -3,6 +3,7 @@
113 from .base import BaseEnvironment
114 from ..utils import (
115 ErrorExit,
116+ get_juju_major_version,
117 parse_constraints,
118 )
119
120@@ -26,6 +27,7 @@
121 self.options = options
122 self.api_endpoint = endpoint
123 self.client = None
124+ self.juju_version = get_juju_major_version()
125
126 def add_machine(self, series="", constraints={}):
127 """Add a top level machine to the Juju environment.
128@@ -54,11 +56,16 @@
129 if self.client:
130 self.client.close()
131
132+
133 def connect(self):
134 self.log.debug("Connecting to environment...")
135+ if self.juju_version == 1:
136+ cmd = ["juju", "api-endpoints"]
137+ else:
138+ cmd = ["juju", "get-model-config"]
139 with open("/dev/null", 'w') as fh:
140 self._check_call(
141- self._named_env(["juju", "api-endpoints"]),
142+ self._named_env(cmd),
143 self.log, "Error getting env api endpoints, env bootstrapped?",
144 stderr=fh)
145
146
147=== modified file 'deployer/env/watchers.py'
148--- deployer/env/watchers.py 2014-02-18 12:16:46 +0000
149+++ deployer/env/watchers.py 2016-04-01 12:01:16 +0000
150@@ -2,7 +2,11 @@
151
152 from jujuclient import WatchWrapper
153
154-from ..utils import ErrorExit
155+from ..utils import ErrorExit, get_juju_major_version
156+
157+# _status_map provides a translation of Juju 2 status codes to the closest
158+# Juju 1 equivalent. Only defines codes that need translation.
159+_status_map = {'idle': 'started'}
160
161
162 class WaitForMachineTermination(WatchWrapper):
163@@ -66,7 +70,15 @@
164 on_errors = self.on_errors
165 units_in_error = self.units_in_error
166 for unit_name, data in self.units.items():
167- status = data['Status']
168+ try:
169+ status = data['Status']
170+ except KeyError:
171+ # 'Status' has been removed from newer versions of Juju.
172+ # Respond with the closest status parameter, translating it
173+ # through the _status_map. If the status value is not in
174+ # _status_map, just use the original value.
175+ status = data['JujuStatus']['Current']
176+ status = _status_map.get(status, status)
177 if status == 'error':
178 if unit_name not in units_in_error:
179 units_in_error.append(unit_name)
180
181=== modified file 'deployer/tests/test_goenv.py'
182--- deployer/tests/test_goenv.py 2015-09-23 15:32:46 +0000
183+++ deployer/tests/test_goenv.py 2016-04-01 12:01:16 +0000
184@@ -72,7 +72,12 @@
185 services)
186 for s in services:
187 for k, u in status['services'][s]['units'].items():
188- self.assertIn(u['agent-state'], ("allocating", "started"))
189+ try:
190+ self.assertIn(u['agent-state'],
191+ ("allocating", "pending", "started"))
192+ except KeyError:
193+ self.assertIn(u['jujustatus']['Current'],
194+ ("allocating", "idle"))
195
196 def test_add_machine(self):
197 machine_name = self.env.add_machine()
198
199=== modified file 'deployer/utils.py'
200--- deployer/utils.py 2016-01-11 13:35:47 +0000
201+++ deployer/utils.py 2016-04-01 12:01:16 +0000
202@@ -244,6 +244,22 @@
203 jhome = path_join(os.environ.get('HOME'), '.juju')
204 return jhome
205
206+_juju_major_version = None
207+def get_juju_major_version():
208+ global _juju_major_version
209+ if _juju_major_version is None:
210+ log = logging.getLogger("deployer.utils")
211+ _juju_major_version = int(_check_call(
212+ ["juju", "--version"], log).split('.')[0])
213+ return _juju_major_version
214+
215+
216+def get_juju_default_env():
217+ if get_juju_major_version() == 1:
218+ return os.environ.get("JUJU_ENV")
219+ else:
220+ return os.environ.get("JUJU_MODEL")
221+
222
223 def _check_call(params, log, *args, **kw):
224 max_retry = kw.get('max_retry', None)
225@@ -365,20 +381,25 @@
226 """
227 if param_env_name:
228 return param_env_name
229- elif os.environ.get("JUJU_ENV"):
230- return os.environ['JUJU_ENV']
231-
232- juju_home = _get_juju_home()
233- env_ptr = os.path.join(juju_home, "current-environment")
234- if os.path.exists(env_ptr):
235- with open(env_ptr) as fh:
236- return fh.read().strip()
237-
238- with open(os.path.join(juju_home, 'environments.yaml')) as fh:
239- conf = yaml_load(fh.read())
240- if not 'default' in conf:
241- raise ValueError("No Environment specified")
242- return conf['default']
243+ elif get_juju_default_env():
244+ return get_juju_default_env()
245+
246+ if get_juju_major_version() == 1:
247+ juju_home = _get_juju_home()
248+ env_ptr = os.path.join(juju_home, "current-environment")
249+ if os.path.exists(env_ptr):
250+ with open(env_ptr) as fh:
251+ return fh.read().strip()
252+
253+ with open(os.path.join(juju_home, 'environments.yaml')) as fh:
254+ conf = yaml_load(fh.read())
255+ if not 'default' in conf:
256+ raise ValueError("No Environment specified")
257+ return conf['default']
258+ else:
259+ # Return the juju2 controller:model combo
260+ log = logging.getLogger("deployer.utils")
261+ return _check_call(["juju", "switch"], log).strip()
262
263
264 def x_in_y(x, y):

Subscribers

People subscribed via source and target branches