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