]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | from abc import ABCMeta, abstractmethod |
31f18b77 | 2 | from cStringIO import StringIO |
7c673cae FG |
3 | import json |
4 | ||
31f18b77 FG |
5 | from conn import get_gateway_connection |
6 | ||
7c673cae FG |
7 | class Cluster: |
8 | """ interface to run commands against a distinct ceph cluster """ | |
9 | __metaclass__ = ABCMeta | |
10 | ||
11 | @abstractmethod | |
12 | def admin(self, args = None, **kwargs): | |
13 | """ execute a radosgw-admin command """ | |
14 | pass | |
15 | ||
16 | class Gateway: | |
17 | """ interface to control a single radosgw instance """ | |
18 | __metaclass__ = ABCMeta | |
19 | ||
20 | def __init__(self, host = None, port = None, cluster = None, zone = None, proto = 'http', connection = None): | |
21 | self.host = host | |
22 | self.port = port | |
23 | self.cluster = cluster | |
24 | self.zone = zone | |
25 | self.proto = proto | |
26 | self.connection = connection | |
27 | ||
28 | @abstractmethod | |
29 | def start(self, args = []): | |
30 | """ start the gateway with the given args """ | |
31 | pass | |
32 | ||
33 | @abstractmethod | |
34 | def stop(self): | |
35 | """ stop the gateway """ | |
36 | pass | |
37 | ||
38 | def endpoint(self): | |
39 | return '%s://%s:%d' % (self.proto, self.host, self.port) | |
40 | ||
41 | class SystemObject: | |
42 | """ interface for system objects, represented in json format and | |
43 | manipulated with radosgw-admin commands """ | |
44 | __metaclass__ = ABCMeta | |
45 | ||
46 | def __init__(self, data = None, uuid = None): | |
47 | self.data = data | |
48 | self.id = uuid | |
49 | if data: | |
50 | self.load_from_json(data) | |
51 | ||
52 | @abstractmethod | |
53 | def build_command(self, command): | |
54 | """ return the command line for the given command, including arguments | |
55 | to specify this object """ | |
56 | pass | |
57 | ||
58 | @abstractmethod | |
59 | def load_from_json(self, data): | |
60 | """ update internal state based on json data """ | |
61 | pass | |
62 | ||
63 | def command(self, cluster, cmd, args = None, **kwargs): | |
64 | """ run the given command and return the output and retcode """ | |
65 | args = self.build_command(cmd) + (args or []) | |
66 | return cluster.admin(args, **kwargs) | |
67 | ||
68 | def json_command(self, cluster, cmd, args = None, **kwargs): | |
69 | """ run the given command, parse the output and return the resulting | |
70 | data and retcode """ | |
31f18b77 | 71 | s, r = self.command(cluster, cmd, args or [], **kwargs) |
7c673cae FG |
72 | if r == 0: |
73 | output = s.decode('utf-8') | |
74 | output = output[output.find('{'):] # trim extra output before json | |
75 | data = json.loads(output) | |
76 | self.load_from_json(data) | |
77 | self.data = data | |
31f18b77 | 78 | return self.data, r |
7c673cae FG |
79 | |
80 | # mixins for supported commands | |
81 | class Create(object): | |
82 | def create(self, cluster, args = None, **kwargs): | |
83 | """ create the object with the given arguments """ | |
84 | return self.json_command(cluster, 'create', args, **kwargs) | |
85 | ||
86 | class Delete(object): | |
87 | def delete(self, cluster, args = None, **kwargs): | |
88 | """ delete the object """ | |
89 | # not json_command() because delete has no output | |
31f18b77 | 90 | _, r = self.command(cluster, 'delete', args, **kwargs) |
7c673cae FG |
91 | if r == 0: |
92 | self.data = None | |
93 | return r | |
94 | ||
95 | class Get(object): | |
96 | def get(self, cluster, args = None, **kwargs): | |
97 | """ read the object from storage """ | |
98 | kwargs['read_only'] = True | |
99 | return self.json_command(cluster, 'get', args, **kwargs) | |
100 | ||
101 | class Set(object): | |
102 | def set(self, cluster, data, args = None, **kwargs): | |
103 | """ set the object by json """ | |
104 | kwargs['stdin'] = StringIO(json.dumps(data)) | |
105 | return self.json_command(cluster, 'set', args, **kwargs) | |
106 | ||
107 | class Modify(object): | |
108 | def modify(self, cluster, args = None, **kwargs): | |
109 | """ modify the object with the given arguments """ | |
110 | return self.json_command(cluster, 'modify', args, **kwargs) | |
111 | ||
112 | class CreateDelete(Create, Delete): pass | |
113 | class GetSet(Get, Set): pass | |
114 | ||
115 | class Zone(SystemObject, SystemObject.CreateDelete, SystemObject.GetSet, SystemObject.Modify): | |
116 | def __init__(self, name, zonegroup = None, cluster = None, data = None, zone_id = None, gateways = None): | |
117 | self.name = name | |
118 | self.zonegroup = zonegroup | |
119 | self.cluster = cluster | |
120 | self.gateways = gateways or [] | |
121 | super(Zone, self).__init__(data, zone_id) | |
122 | ||
123 | def zone_arg(self): | |
124 | """ command-line argument to specify this zone """ | |
125 | return ['--rgw-zone', self.name] | |
126 | ||
127 | def zone_args(self): | |
128 | """ command-line arguments to specify this zone/zonegroup/realm """ | |
129 | args = self.zone_arg() | |
130 | if self.zonegroup: | |
131 | args += self.zonegroup.zonegroup_args() | |
132 | return args | |
133 | ||
134 | def build_command(self, command): | |
135 | """ build a command line for the given command and args """ | |
136 | return ['zone', command] + self.zone_args() | |
137 | ||
138 | def load_from_json(self, data): | |
139 | """ load the zone from json """ | |
140 | self.id = data['id'] | |
141 | self.name = data['name'] | |
142 | ||
143 | def start(self, args = None): | |
144 | """ start all gateways """ | |
145 | for g in self.gateways: | |
146 | g.start(args) | |
147 | ||
148 | def stop(self): | |
149 | """ stop all gateways """ | |
150 | for g in self.gateways: | |
151 | g.stop() | |
152 | ||
153 | def period(self): | |
154 | return self.zonegroup.period if self.zonegroup else None | |
155 | ||
156 | def realm(self): | |
157 | return self.zonegroup.realm() if self.zonegroup else None | |
158 | ||
31f18b77 FG |
159 | def is_read_only(self): |
160 | return False | |
161 | ||
162 | def tier_type(self): | |
163 | raise NotImplementedError | |
164 | ||
165 | def has_buckets(self): | |
166 | return True | |
167 | ||
168 | def get_conn(self, credentials): | |
169 | return ZoneConn(self, credentials) # not implemented, but can be used | |
170 | ||
171 | class ZoneConn(object): | |
172 | def __init__(self, zone, credentials): | |
173 | self.zone = zone | |
174 | self.name = zone.name | |
175 | """ connect to the zone's first gateway """ | |
176 | if isinstance(credentials, list): | |
177 | self.credentials = credentials[0] | |
178 | else: | |
179 | self.credentials = credentials | |
180 | ||
181 | if self.zone.gateways is not None: | |
182 | self.conn = get_gateway_connection(self.zone.gateways[0], self.credentials) | |
183 | ||
184 | def get_connection(self): | |
185 | return self.conn | |
186 | ||
187 | def get_bucket(self, bucket_name, credentials): | |
188 | raise NotImplementedError | |
189 | ||
190 | def check_bucket_eq(self, zone, bucket_name): | |
191 | raise NotImplementedError | |
192 | ||
7c673cae FG |
193 | class ZoneGroup(SystemObject, SystemObject.CreateDelete, SystemObject.GetSet, SystemObject.Modify): |
194 | def __init__(self, name, period = None, data = None, zonegroup_id = None, zones = None, master_zone = None): | |
195 | self.name = name | |
196 | self.period = period | |
197 | self.zones = zones or [] | |
198 | self.master_zone = master_zone | |
199 | super(ZoneGroup, self).__init__(data, zonegroup_id) | |
31f18b77 FG |
200 | self.rw_zones = [] |
201 | self.ro_zones = [] | |
202 | self.zones_by_type = {} | |
203 | for z in self.zones: | |
204 | if z.is_read_only(): | |
205 | self.ro_zones.append(z) | |
206 | else: | |
207 | self.rw_zones.append(z) | |
7c673cae FG |
208 | |
209 | def zonegroup_arg(self): | |
210 | """ command-line argument to specify this zonegroup """ | |
211 | return ['--rgw-zonegroup', self.name] | |
212 | ||
213 | def zonegroup_args(self): | |
214 | """ command-line arguments to specify this zonegroup/realm """ | |
215 | args = self.zonegroup_arg() | |
216 | realm = self.realm() | |
217 | if realm: | |
218 | args += realm.realm_arg() | |
219 | return args | |
220 | ||
221 | def build_command(self, command): | |
222 | """ build a command line for the given command and args """ | |
223 | return ['zonegroup', command] + self.zonegroup_args() | |
224 | ||
225 | def zone_by_id(self, zone_id): | |
226 | """ return the matching zone by id """ | |
227 | for zone in self.zones: | |
228 | if zone.id == zone_id: | |
229 | return zone | |
230 | return None | |
231 | ||
232 | def load_from_json(self, data): | |
233 | """ load the zonegroup from json """ | |
234 | self.id = data['id'] | |
235 | self.name = data['name'] | |
236 | master_id = data['master_zone'] | |
237 | if not self.master_zone or master_id != self.master_zone.id: | |
238 | self.master_zone = self.zone_by_id(master_id) | |
239 | ||
240 | def add(self, cluster, zone, args = None, **kwargs): | |
241 | """ add an existing zone to the zonegroup """ | |
242 | args = zone.zone_arg() + (args or []) | |
31f18b77 | 243 | data, r = self.json_command(cluster, 'add', args, **kwargs) |
7c673cae FG |
244 | if r == 0: |
245 | zone.zonegroup = self | |
246 | self.zones.append(zone) | |
31f18b77 | 247 | return data, r |
7c673cae FG |
248 | |
249 | def remove(self, cluster, zone, args = None, **kwargs): | |
250 | """ remove an existing zone from the zonegroup """ | |
251 | args = zone.zone_arg() + (args or []) | |
31f18b77 | 252 | data, r = self.json_command(cluster, 'remove', args, **kwargs) |
7c673cae FG |
253 | if r == 0: |
254 | zone.zonegroup = None | |
255 | self.zones.remove(zone) | |
31f18b77 | 256 | return data, r |
7c673cae FG |
257 | |
258 | def realm(self): | |
259 | return self.period.realm if self.period else None | |
260 | ||
261 | class Period(SystemObject, SystemObject.Get): | |
262 | def __init__(self, realm = None, data = None, period_id = None, zonegroups = None, master_zonegroup = None): | |
263 | self.realm = realm | |
264 | self.zonegroups = zonegroups or [] | |
265 | self.master_zonegroup = master_zonegroup | |
266 | super(Period, self).__init__(data, period_id) | |
267 | ||
268 | def zonegroup_by_id(self, zonegroup_id): | |
269 | """ return the matching zonegroup by id """ | |
270 | for zonegroup in self.zonegroups: | |
271 | if zonegroup.id == zonegroup_id: | |
272 | return zonegroup | |
273 | return None | |
274 | ||
275 | def build_command(self, command): | |
276 | """ build a command line for the given command and args """ | |
277 | return ['period', command] | |
278 | ||
279 | def load_from_json(self, data): | |
280 | """ load the period from json """ | |
281 | self.id = data['id'] | |
282 | master_id = data['master_zonegroup'] | |
283 | if not self.master_zonegroup or master_id != self.master_zonegroup.id: | |
284 | self.master_zonegroup = self.zonegroup_by_id(master_id) | |
285 | ||
286 | def update(self, zone, args = None, **kwargs): | |
287 | """ run 'radosgw-admin period update' on the given zone """ | |
288 | assert(zone.cluster) | |
289 | args = zone.zone_args() + (args or []) | |
290 | if kwargs.pop('commit', False): | |
291 | args.append('--commit') | |
292 | return self.json_command(zone.cluster, 'update', args, **kwargs) | |
293 | ||
294 | def commit(self, zone, args = None, **kwargs): | |
295 | """ run 'radosgw-admin period commit' on the given zone """ | |
296 | assert(zone.cluster) | |
297 | args = zone.zone_args() + (args or []) | |
298 | return self.json_command(zone.cluster, 'commit', args, **kwargs) | |
299 | ||
300 | class Realm(SystemObject, SystemObject.CreateDelete, SystemObject.GetSet): | |
301 | def __init__(self, name, period = None, data = None, realm_id = None): | |
302 | self.name = name | |
303 | self.current_period = period | |
304 | super(Realm, self).__init__(data, realm_id) | |
305 | ||
306 | def realm_arg(self): | |
307 | """ return the command-line arguments that specify this realm """ | |
308 | return ['--rgw-realm', self.name] | |
309 | ||
310 | def build_command(self, command): | |
311 | """ build a command line for the given command and args """ | |
312 | return ['realm', command] + self.realm_arg() | |
313 | ||
314 | def load_from_json(self, data): | |
315 | """ load the realm from json """ | |
316 | self.id = data['id'] | |
317 | ||
318 | def pull(self, cluster, gateway, credentials, args = [], **kwargs): | |
319 | """ pull an existing realm from the given gateway """ | |
320 | args += ['--url', gateway.endpoint()] | |
321 | args += credentials.credential_args() | |
322 | return self.json_command(cluster, 'pull', args, **kwargs) | |
323 | ||
324 | def master_zonegroup(self): | |
325 | """ return the current period's master zonegroup """ | |
326 | if self.current_period is None: | |
327 | return None | |
328 | return self.current_period.master_zonegroup | |
329 | ||
330 | def meta_master_zone(self): | |
331 | """ return the current period's metadata master zone """ | |
332 | zonegroup = self.master_zonegroup() | |
333 | if zonegroup is None: | |
334 | return None | |
335 | return zonegroup.master_zone | |
336 | ||
337 | class Credentials: | |
338 | def __init__(self, access_key, secret): | |
339 | self.access_key = access_key | |
340 | self.secret = secret | |
341 | ||
342 | def credential_args(self): | |
343 | return ['--access-key', self.access_key, '--secret', self.secret] | |
344 | ||
345 | class User(SystemObject): | |
346 | def __init__(self, uid, data = None, name = None, credentials = None): | |
347 | self.name = name | |
348 | self.credentials = credentials or [] | |
349 | super(User, self).__init__(data, uid) | |
350 | ||
351 | def user_arg(self): | |
352 | """ command-line argument to specify this user """ | |
353 | return ['--uid', self.id] | |
354 | ||
355 | def build_command(self, command): | |
356 | """ build a command line for the given command and args """ | |
357 | return ['user', command] + self.user_arg() | |
358 | ||
359 | def load_from_json(self, data): | |
360 | """ load the user from json """ | |
361 | self.id = data['user_id'] | |
362 | self.name = data['display_name'] | |
363 | self.credentials = [Credentials(k['access_key'], k['secret_key']) for k in data['keys']] | |
364 | ||
365 | def create(self, zone, args = None, **kwargs): | |
366 | """ create the user with the given arguments """ | |
367 | assert(zone.cluster) | |
368 | args = zone.zone_args() + (args or []) | |
369 | return self.json_command(zone.cluster, 'create', args, **kwargs) | |
370 | ||
371 | def info(self, zone, args = None, **kwargs): | |
372 | """ read the user from storage """ | |
373 | assert(zone.cluster) | |
374 | args = zone.zone_args() + (args or []) | |
375 | kwargs['read_only'] = True | |
376 | return self.json_command(zone.cluster, 'info', args, **kwargs) | |
377 | ||
378 | def delete(self, zone, args = None, **kwargs): | |
379 | """ delete the user """ | |
380 | assert(zone.cluster) | |
381 | args = zone.zone_args() + (args or []) | |
382 | return self.command(zone.cluster, 'delete', args, **kwargs) |