Skip to content

Commit acd0cd8

Browse files
committed
labextension start/stop works
use lists because dictionaries in javascript are too hard load default clusters for profiles
1 parent 2613a24 commit acd0cd8

File tree

10 files changed

+311
-122
lines changed

10 files changed

+311
-122
lines changed

ipyparallel/cluster/cluster.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ def load_clusters(
740740
profile_dir=None,
741741
profiles=None,
742742
profile=None,
743+
init_default_clusters=False,
743744
**kwargs,
744745
):
745746
"""Populate a ClusterManager from cluster files on disk
@@ -749,6 +750,10 @@ def load_clusters(
749750
Default is to find clusters in all IPython profiles,
750751
but profile directories or profile names can be specified explicitly.
751752
753+
If `init_default_clusters` is True,
754+
a stopped Cluster object is loaded for every profile dir
755+
with cluster_id="" if no running cluster is found.
756+
752757
Priority:
753758
754759
- profile_dirs list
@@ -772,8 +777,24 @@ def load_clusters(
772777
# totally unspecified, default to all
773778
profile_dirs = _all_profile_dirs()
774779

780+
by_cluster_file = {c.cluster_file: c for c in self._clusters.values()}
775781
for profile_dir in profile_dirs:
776-
for cluster_file in self._cluster_files_in_profile_dir(profile_dir):
782+
cluster_files = self._cluster_files_in_profile_dir(profile_dir)
783+
# load default cluster for each profile
784+
# TODO: only if it has any ipyparallel config files
785+
# *or* it's the default profile
786+
if init_default_clusters and not cluster_files:
787+
788+
cluster = Cluster(profile_dir=profile_dir, cluster_id="")
789+
cluster_key = self._cluster_key(cluster)
790+
if cluster_key not in self._clusters:
791+
self._clusters[cluster_key] = cluster
792+
793+
for cluster_file in cluster_files:
794+
if cluster_file in by_cluster_file:
795+
# already loaded, skip it
796+
continue
797+
self.log.debug(f"Loading cluster file {cluster_file}")
777798
try:
778799
cluster = Cluster.from_file(cluster_file, parent=self)
779800
except Exception as e:

ipyparallel/nbextension/handlers.py

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,24 @@ class ClusterHandler(APIHandler):
2121
def cluster_manager(self):
2222
return self.settings['cluster_manager']
2323

24-
def cluster_model(self, cluster):
25-
"""Create a JSONable cluster model"""
24+
def cluster_model(self, key, cluster):
25+
"""Create a JSONable cluster model
26+
27+
Adds additional fields to make things easier on the client
28+
"""
2629
d = cluster.to_dict()
2730
profile_dir = d['cluster']['profile_dir']
2831
# provide abbreviated profile info
2932
d['cluster']['profile'] = abbreviate_profile_dir(profile_dir)
33+
34+
# top-level fields, added for easier client-side logic
35+
# add 'id' key, since typescript is bad at key-value pairs
36+
# so we return a list instead of a dict
37+
d['id'] = key
38+
# add total engine count
39+
d['engines']['n'] = sum(es.get('n', 0) for es in d['engines']['sets'].values())
40+
# add cluster file
41+
d['cluster_file'] = cluster.cluster_file
3042
return d
3143

3244

@@ -39,18 +51,47 @@ class ClusterListHandler(ClusterHandler):
3951
@web.authenticated
4052
def get(self):
4153
# currently reloads everything from disk. Is that what we want?
42-
clusters = self.cluster_manager.load_clusters()
43-
self.finish(
44-
{key: self.cluster_model(cluster) for key, cluster in clusters.items()}
54+
clusters = self.cluster_manager.load_clusters(init_default_clusters=True)
55+
56+
def sort_key(model):
57+
"""Sort clusters
58+
59+
order:
60+
- running
61+
- default profile
62+
- default cluster id
63+
"""
64+
running = True if model.get("controller") else False
65+
profile = model['cluster']['profile']
66+
cluster_id = model['cluster']['cluster_id']
67+
default_profile = profile == 'default'
68+
default_cluster = cluster_id == ''
69+
70+
return (
71+
not running,
72+
not default_profile,
73+
not default_cluster,
74+
profile,
75+
cluster_id,
76+
)
77+
78+
self.write(
79+
json.dumps(
80+
sorted(
81+
[
82+
self.cluster_model(key, cluster)
83+
for key, cluster in clusters.items()
84+
],
85+
key=sort_key,
86+
)
87+
)
4588
)
4689

4790
@web.authenticated
4891
def post(self):
4992
body = self.get_json_body() or {}
50-
# profile
51-
# cluster_id
5293
cluster_id, cluster = self.cluster_manager.new_cluster(**body)
53-
self.write(json.dumps({}))
94+
self.write(json.dumps(self.cluster_model(cluster_id, cluster)))
5495

5596

5697
class ClusterActionHandler(ClusterHandler):
@@ -71,21 +112,24 @@ def get_cluster(self, cluster_key):
71112
@web.authenticated
72113
async def post(self, cluster_key):
73114
cluster = self.get_cluster(cluster_key)
74-
n = self.get_argument('n', default=None)
115+
body = self.get_json_body() or {}
116+
n = body.get("n", None)
117+
if n is not None and not isinstance(n, int):
118+
raise web.HTTPError(400, f"n must be an integer, not {n!r}")
75119
await cluster.start_cluster(n=n)
76-
self.write(json.dumps(self.cluster_model(cluster)))
120+
self.write(json.dumps(self.cluster_model(cluster_key, cluster)))
77121

78122
@web.authenticated
79123
async def get(self, cluster_key):
80124
cluster = self.get_cluster(cluster_key)
81-
self.write(json.dumps(self.cluster_model(cluster)))
125+
self.write(json.dumps(self.cluster_model(cluster_key, cluster)))
82126

83127
@web.authenticated
84128
async def delete(self, cluster_key):
85129
cluster = self.get_cluster(cluster_key)
86130
await cluster.stop_cluster()
87131
self.cluster_manager.remove_cluster(cluster_key)
88-
self.write(json.dumps(self.cluster_model(cluster)))
132+
self.set_status(204)
89133

90134

91135
# -----------------------------------------------------------------------------

ipyparallel/nbextension/static/clusterlist.js

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -60,36 +60,31 @@ define(["base/js/namespace", "jquery", "base/js/utils"], function (
6060
ClusterList.prototype.load_list_success = function (data, status, xhr) {
6161
this.clear_list();
6262
var len = data.length;
63-
console.log("cluster list", data);
64-
for (var cluster_key in data) {
65-
if (!data.hasOwnProperty(cluster_key)) {
66-
continue;
67-
}
63+
for (var cluster of data) {
6864
var element = $("<div/>");
69-
var item = new ClusterItem(element, cluster_key, this.options);
70-
item.update_state(data[cluster_key]);
65+
var item = new ClusterItem(element, cluster, this, this.options);
7166
element.data("item", item);
7267
this.element.append(element);
7368
}
7469
};
7570

76-
var ClusterItem = function (element, cluster_key, options) {
71+
var ClusterItem = function (element, cluster, cluster_list, options) {
7772
this.element = $(element);
78-
this.cluster_key = cluster_key;
73+
this.cluster_list = cluster_list;
7974
this.base_url = options.base_url || utils.get_body_data("baseUrl");
8075
this.notebook_path =
8176
options.notebook_path || utils.get_body_data("notebookPath");
82-
this.data = null;
77+
this.update_state(cluster);
8378
this.style();
8479
};
8580

8681
ClusterItem.prototype.style = function () {
8782
this.element.addClass("list_item").addClass("row");
8883
};
8984

90-
ClusterItem.prototype.update_state = function (data) {
91-
this.data = data;
92-
if (data.controller.state) {
85+
ClusterItem.prototype.update_state = function (cluster) {
86+
this.cluster = cluster;
87+
if (cluster.controller && cluster.controller.state) {
9388
this.state_running();
9489
} else {
9590
this.state_stopped();
@@ -100,10 +95,10 @@ define(["base/js/namespace", "jquery", "base/js/utils"], function (
10095
var that = this;
10196
var profile_col = $("<div/>")
10297
.addClass("profile_col col-xs-2")
103-
.text(this.data.profile);
98+
.text(this.cluster.cluster.profile);
10499
var cluster_id_col = $("<div/>")
105100
.addClass("cluster_id_col col-xs-2")
106-
.text(this.data.cluster_id);
101+
.text(this.cluster.cluster.cluster_id);
107102
var status_col = $("<div/>")
108103
.addClass("status_col col-xs-3")
109104
.text("stopped");
@@ -134,9 +129,12 @@ define(["base/js/namespace", "jquery", "base/js/utils"], function (
134129
if (!/^\d+$/.test(n) && n.length > 0) {
135130
status_col.text("invalid engine #");
136131
} else {
132+
if (n === "") {
133+
n = null;
134+
}
137135
var settings = {
138136
cache: false,
139-
data: { n: n },
137+
data: JSON.stringify({ n: n }),
140138
type: "POST",
141139
dataType: "json",
142140
success: function (data, status, xhr) {
@@ -151,7 +149,7 @@ define(["base/js/namespace", "jquery", "base/js/utils"], function (
151149
var url = utils.url_join_encode(
152150
that.base_url,
153151
"ipyparallel/clusters",
154-
that.cluster_key
152+
that.cluster.id
155153
);
156154
ajax(url, settings);
157155
}
@@ -163,25 +161,17 @@ define(["base/js/namespace", "jquery", "base/js/utils"], function (
163161

164162
var profile_col = $("<div/>")
165163
.addClass("profile_col col-xs-2")
166-
.text(this.data.cluster.profile);
164+
.text(this.cluster.cluster.profile);
167165
var cluster_id_col = $("<div/>")
168166
.addClass("cluster_id_col col-xs-2")
169-
.text(this.data.cluster.cluster_id);
167+
.text(this.cluster.cluster.cluster_id);
170168
var status_col = $("<div/>")
171169
.addClass("status_col col-xs-3")
172170
.text("running");
173171

174-
// calculate n_engines
175-
var n_engines = 0;
176-
$.map(this.data.engines.sets, function (engine_set) {
177-
if (engine_set.n) {
178-
n_engines += engine_set.n;
179-
}
180-
});
181-
182172
var engines_col = $("<div/>")
183173
.addClass("engines_col col-xs-3")
184-
.text(n_engines);
174+
.text(this.cluster.engines.n);
185175
var stop_button = $("<button/>")
186176
.addClass("btn btn-default btn-xs")
187177
.text("Stop");
@@ -204,7 +194,7 @@ define(["base/js/namespace", "jquery", "base/js/utils"], function (
204194
type: "DELETE",
205195
dataType: "json",
206196
success: function (data, status, xhr) {
207-
that.update_state(data);
197+
that.cluster_list.load_list();
208198
},
209199
error: function (xhr, status, error) {
210200
utils.log_ajax_error(xhr, status, error),
@@ -215,7 +205,7 @@ define(["base/js/namespace", "jquery", "base/js/utils"], function (
215205
var url = utils.url_join_encode(
216206
that.base_url,
217207
"ipyparallel/clusters",
218-
that.cluster_key
208+
that.cluster.id
219209
);
220210
ajax(url, settings);
221211
});

ipyparallel/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ def _all_profile_dirs():
683683
with os.scandir(get_ipython_dir()) as paths:
684684
for path in paths:
685685
if path.is_dir() and path.name.startswith('profile_'):
686-
profile_dirs.append(path)
686+
profile_dirs.append(path.path)
687687
return profile_dirs
688688

689689

0 commit comments

Comments
 (0)