Merge bootstack-ops:juju-select-largest-model into bootstack-ops:master

Proposed by David O Neill
Status: Rejected
Rejected by: Giuseppe Petralia
Proposed branch: bootstack-ops:juju-select-largest-model
Merge into: bootstack-ops:master
Diff against target: 290 lines (+234/-6) (has conflicts)
1 file modified
bootstack-ops/cloud_report.py (+234/-6)
Conflict in bootstack-ops/cloud_report.py
Reviewer Review Type Date Requested Status
Giuseppe Petralia Disapprove
Zachary Zehring (community) Needs Fixing
Drew Freiberger (community) Needs Fixing
Review via email: mp+366113@code.launchpad.net

Commit message

Switches the largest juju model prior to other juju commands

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Giuseppe Petralia (peppepetra) wrote :

Comments inline

Revision history for this message
Giuseppe Petralia (peppepetra) :
review: Needs Fixing
Revision history for this message
Drew Freiberger (afreiberger) wrote :

comments inline

Revision history for this message
Paul Goins (vultaire) :
Revision history for this message
Paul Goins (vultaire) :
Revision history for this message
Drew Freiberger (afreiberger) wrote :

merge conflict in the code

review: Needs Fixing
Revision history for this message
Zachary Zehring (zzehring) wrote :

inline comments. have some more but will save when merge conflict is resolved.

review: Needs Fixing
Revision history for this message
Andrea Ieri (aieri) wrote :

I agree with Paul, we have to take into account clouds that have multiple models.
Aside from CDK clusters (which may or may not need to be billed), we also have clouds that use separate models for ceph backup clusters.
We should perhaps think about {white,black}listing models instead.
See also https://bugs.launchpad.net/bootstack-ops/+bug/1842356

Revision history for this message
Giuseppe Petralia (peppepetra) wrote :
review: Disapprove

Unmerged commits

2b622cd... by David O Neill

Switches the largest juju model prior to other juju commands

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/bootstack-ops/cloud_report.py b/bootstack-ops/cloud_report.py
2index f13a5d3..d278db5 100644
3--- a/bootstack-ops/cloud_report.py
4+++ b/bootstack-ops/cloud_report.py
5@@ -43,6 +43,8 @@ import json
6 import click
7 import subprocess
8 import yaml
9+import re
10+
11
12 from maas.client import login, connect
13 from flask import Flask, jsonify
14@@ -382,22 +384,38 @@ class Juju(object):
15 def disconnect(self):
16 pass
17
18- def _list_machines(self):
19+ def run_juju_command(self, command, **kwargs):
20+ """
21+ Run a juju command with args and specify the output format
22+
23+ :param command: Juju command
24+ :param format: the output format, tabulaur (None), json, yaml
25+ :param commandargs: argument for the specific command
26+ :return: string or None
27+ """
28 if not JUJU_DATA:
29 print("Please export JUJU_DATA env variable before running the script")
30 sys.exit(1)
31
32- machines = []
33 env = os.environ.copy()
34 env["JUJU_DATA"] = JUJU_DATA
35
36+ format = "--format=" + kwargs['format'] if "format" in kwargs else None
37+
38 if os.path.isfile('/snap/bin/juju'):
39- cmd = ['/snap/bin/juju', 'status', '--format=yaml']
40+ cmd = ['/snap/bin/juju', command]
41 elif os.path.isfile("/usr/bin/juju"):
42- cmd = ['/usr/bin/juju', 'status', '--format=yaml']
43+ cmd = ['/usr/bin/juju', command]
44 else:
45- cmd = ['snap run juju', 'status', '--format=yaml']
46+ cmd = ['snap run juju', command]
47
48+ if "commandargs" in kwargs:
49+ cmd.append(kwargs['commandargs'])
50+
51+ if format is not None:
52+ cmd.append(format)
53+
54+<<<<<<< bootstack-ops/cloud_report.py
55 for i in range(0, RETRY):
56 try:
57 output = subprocess.check_output(cmd, timeout=30, env=env)
58@@ -409,6 +427,114 @@ class Juju(object):
59 time.sleep(SLEEP * i)
60
61 return machines
62+=======
63+ try:
64+ return subprocess.check_output(cmd, env=env)
65+ except subprocess.CalledProcessError as e:
66+ print("Exception calling juju status command. {}".format(e))
67+ # implicit, but lets do it anyway
68+ return None
69+
70+
71+ def _list_machines(self):
72+ res = self.run_juju_command("status", format="yaml")
73+
74+ if res is None:
75+ print("Failed to get machine list, @see run_juju_command")
76+ return None
77+
78+ return yaml.safe_load(res)
79+
80+
81+ def switch_model(self, model):
82+ """
83+ Switches to a given model
84+ :param model: the model to switch to
85+ """
86+
87+ res = self.run_juju_command("switch", commandargs=model)
88+
89+ if res is None:
90+ print("Exception calling juju switch, @see run_juju_command")
91+ return None
92+
93+ def list_models(self):
94+ """
95+ Gets the output of juju list-models
96+ :returns models: string of the command output
97+ """
98+
99+ models = self.run_juju_command("list-models")
100+
101+ if models is None:
102+ print("Exception calling juju list-models command, @see run_juju_command")
103+ return None
104+
105+ return models
106+
107+ def get_model_and_sizes(self):
108+ """
109+ From the output of juju list-models we create a dict of the format
110+ {
111+ 'model-name': machine-count
112+ 'model-name': machine-count
113+ 'model-name': machine-count
114+ }
115+ :returns dict: the above format
116+ """
117+ models = self.list_models().decode('utf8').split('\n')
118+ modelsdict = {}
119+ machinescolnum = 0
120+
121+ for line in models:
122+ if line.strip() == "":
123+ continue
124+
125+ if line.startswith('Controller') and machinescolnum == 0:
126+ continue
127+
128+ line = line.replace('\n', ' ').replace('\r', '')
129+ cols = re.split('\s{2,100}', line)
130+
131+ if machinescolnum == 0:
132+ for col in cols:
133+ if col == "Machines":
134+ break
135+ machinescolnum += 1
136+ else:
137+ modelsdict[cols[0]] = int(cols[machinescolnum])
138+
139+ return modelsdict
140+
141+ def switch_to_largest_model(self):
142+ """
143+ Looks through a dict of models in format
144+ {
145+ 'model-name': machine-count
146+ 'model-name': machine-count
147+ 'model-name': machine-count
148+ }
149+ Determiens the largest model from the machine count
150+ Then calls switch_model
151+ """
152+ largest = -1
153+ largestModel = None
154+ models = self.get_model_and_sizes()
155+
156+ if len(models) == 0:
157+ print("No models found ?????")
158+ return
159+
160+ for k, v in models.items():
161+ if v > largest:
162+ largest = v
163+ largestModel = k
164+
165+ if "*" not in largestModel:
166+ self.switch_model(largestModel)
167+ else:
168+ print("Largest model already selected: " + largestModel)
169+>>>>>>> bootstack-ops/cloud_report.py
170
171 def connect(self):
172 return True
173@@ -478,6 +604,108 @@ class Juju(object):
174
175 return m_json
176
177+ def switch_model(self, model):
178+ """
179+ Switches to a given model
180+ :param model: the model to switch to
181+ """
182+
183+ if os.path.isfile('/snap/bin/juju'):
184+ cmd = ['/snap/bin/juju', 'switch', model]
185+ elif os.path.isfile("/usr/bin/juju"):
186+ cmd = ['/usr/bin/juju', 'switch', model]
187+ else:
188+ cmd = ['snap run juju', 'switch', model]
189+
190+ try:
191+ subprocess.check_output(cmd)
192+ except subprocess.CalledProcessError as e:
193+ print("Exception calling juju status command. {}".format(e))
194+
195+ def list_models(self):
196+ """
197+ Gets the output of juju list-models
198+ :returns dict: the above format
199+ """
200+ models = None
201+
202+ if os.path.isfile('/snap/bin/juju'):
203+ cmd = ['/snap/bin/juju', 'list-models']
204+ elif os.path.isfile("/usr/bin/juju"):
205+ cmd = ['/usr/bin/juju', 'list-models']
206+ else:
207+ cmd = ['snap run juju', 'list-models']
208+
209+ try:
210+ models = subprocess.check_output(cmd).decode("utf-8")
211+ except subprocess.CalledProcessError as e:
212+ print("Exception calling juju status command. {}".format(e))
213+
214+ return models
215+
216+ def get_model_and_sizes(self):
217+ """
218+ From the output of juju list-models we create a dict of the format
219+ {
220+ 'model-name': machine-count
221+ 'model-name': machine-count
222+ 'model-name': machine-count
223+ }
224+ :returns dict: the above format
225+ """
226+ models = self.list_models().split('\n')
227+ modelsDict = {}
228+ machinesColnum = 0
229+
230+ for line in models:
231+ if line.strip() == "":
232+ continue
233+
234+ if line.startswith('Controller') and machinesColnum == 0:
235+ continue
236+
237+ line = line.replace('\n', ' ').replace('\r', '')
238+ cols = re.split('\s{2,100}', line)
239+
240+ if machinesColnum == 0:
241+ for col in cols:
242+ if col == "Machines":
243+ break
244+ machinesColnum += 1
245+ else:
246+ modelsDict[cols[0]] = int(cols[machinesColnum])
247+
248+ return modelsDict
249+
250+ def switch_to_largest_model(self):
251+ """
252+ Looks through a dict of models in format
253+ {
254+ 'model-name': machine-count
255+ 'model-name': machine-count
256+ 'model-name': machine-count
257+ }
258+ Determiens the largest model from the machine count
259+ Then calls switch_model
260+ """
261+ largest = -1
262+ largestModel = None
263+ models = self.get_model_and_sizes()
264+
265+ if len(models) == 0:
266+ print("No models found ?????")
267+ return
268+
269+ for k,v in models.items():
270+ if v > largest:
271+ largest = v
272+ largestModel = k
273+
274+ if "*" not in largestModel:
275+ self.switch_model(largestModel)
276+ else:
277+ print("Largest model already selected: " + largestModel)
278+
279 def get_report(self, running=False):
280 """
281 Get the json report for running machines retrieved using Juju cli.
282@@ -485,7 +713,7 @@ class Juju(object):
283 :param running: If it is set to true only machines with status 'running' will be added to the report
284 :return: json report
285 """
286-
287+ self.switch_to_largest_model()
288 machines = self._list_machines()
289 juju_machines = []
290 for m in machines:

Subscribers

People subscribed via source and target branches

to all changes: