]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/rgw/rgw_multi/multisite.py
update sources to v12.1.0
[ceph.git] / ceph / src / test / rgw / rgw_multi / multisite.py
1 from abc import ABCMeta, abstractmethod
2 from cStringIO import StringIO
3 import json
4
5 from conn import get_gateway_connection
6
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 """
71 s, r = self.command(cluster, cmd, args or [], **kwargs)
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
78 return self.data, r
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
90 _, r = self.command(cluster, 'delete', args, **kwargs)
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
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
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)
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)
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 [])
243 data, r = self.json_command(cluster, 'add', args, **kwargs)
244 if r == 0:
245 zone.zonegroup = self
246 self.zones.append(zone)
247 return data, r
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 [])
252 data, r = self.json_command(cluster, 'remove', args, **kwargs)
253 if r == 0:
254 zone.zonegroup = None
255 self.zones.remove(zone)
256 return data, r
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)