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