remove global and rename for import-ability
[tests.git] / system / plcsh_stress_test.py
1 #!/usr/bin/env plcsh
2 #
3 # Test script utility class
4 #
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2006 The Trustees of Princeton University
7 #
8 # $Id$
9 #
10
11 from pprint import pprint
12 from string import letters, digits, punctuation, whitespace
13 from traceback import print_exc
14 from optparse import OptionParser
15 import socket
16 import base64
17 import struct
18 import os
19 import xmlrpclib
20
21 from PLC.Shell import Shell
22
23 from random import Random
24 random = Random()
25
26 # note about namelengths
27 # original version uses full lengths for all fields for testing overflows and things
28 # however for a realistic test, involving a web UI, this is not appropriate, so we
29 # use smaller identifiers
30
31 def randfloat(min = 0.0, max = 1.0):
32     return float(min) + (random.random() * (float(max) - float(min)))
33
34 def randint(min = 0, max = 1):
35     return int(randfloat(min, max + 1))
36
37 # See "2.2 Characters" in the XML specification:
38 #
39 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
40 # avoiding
41 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
42 #
43
44 ascii_xml_chars = map(unichr, [0x9, 0xA])
45 # xmlrpclib uses xml.parsers.expat, which always converts either '\r'
46 # (#xD) or '\n' (#xA) to '\n'. So avoid using '\r', too, if this is
47 # still the case.
48 if xmlrpclib.loads(xmlrpclib.dumps(('\r',)))[0][0] == '\r':
49     ascii_xml_chars.append('\r')
50 ascii_xml_chars += map(unichr, xrange(0x20, 0x7F - 1))
51 low_xml_chars = list(ascii_xml_chars)
52 low_xml_chars += map(unichr, xrange(0x84 + 1, 0x86 - 1))
53 low_xml_chars += map(unichr, xrange(0x9F + 1, 0xFF))
54 valid_xml_chars = list(low_xml_chars)
55 valid_xml_chars += map(unichr, xrange(0xFF + 1, 0xD7FF))
56 valid_xml_chars += map(unichr, xrange(0xE000, 0xFDD0 - 1))
57 valid_xml_chars += map(unichr, xrange(0xFDDF + 1, 0xFFFD))
58
59 def randstr(length, pool = valid_xml_chars, encoding = "utf-8"):
60     sample = random.sample(pool, min(length, len(pool)))
61     while True:
62         s = u''.join(sample)
63         bytes = len(s.encode(encoding))
64         if bytes > length:
65             sample.pop()
66         elif bytes < length:
67             sample += random.sample(pool, min(length - bytes, len(pool)))
68             random.shuffle(sample)
69         else:
70             break
71     return s
72
73 def randhostname(namelengths):
74     # 1. Each part begins and ends with a letter or number.
75     # 2. Each part except the last can contain letters, numbers, or hyphens.
76     # 3. Each part is between 1 and 64 characters, including the trailing dot.
77     # 4. At least two parts.
78     # 5. Last part can only contain between 2 and 6 letters.
79     hostname = 'a' + randstr(namelengths['hostname1'], letters + digits + '-') + '1.' + \
80                'b' + randstr(namelengths['hostname1'], letters + digits + '-') + '2.' + \
81                'c' + randstr(namelengths['hostname2'], letters)
82     return hostname
83
84 def randpath(length):
85     parts = []
86     for i in range(randint(1, 10)):
87         parts.append(randstr(randint(1, 30), ascii_xml_chars))
88     return u'/'.join(parts)[0:length]
89
90 def randemail(namelengths):
91     return (randstr(namelengths['email'], letters + digits) + "@" + randhostname(namelengths)).lower()
92
93 def randkey(namelengths,bits = 2048):
94     ssh_key_types = ["ssh-dss", "ssh-rsa"]
95     key_type = random.sample(ssh_key_types, 1)[0]
96     return ' '.join([key_type,
97                      base64.b64encode(''.join(randstr(bits / 8).encode("utf-8"))),
98                      randemail(namelengths)])
99
100 def random_site(namelengths):
101     try:
102         sitename=randstr(namelengths['sitename'],namelengths['sitename_contents'])
103     except:
104         sitename=randstr(namelengths['sitename'])
105     try:
106         abbreviated_name=randstr(namelengths['abbreviated_name'],namelengths['abbreviated_name_contents'])
107     except:
108         abbreviated_name=randstr(namelengths['abbreviated_name'])
109
110     return {
111         'name': sitename,
112         'abbreviated_name': abbreviated_name,
113         'login_base': randstr(namelengths['login_base'], letters).lower(),
114         'latitude': int(randfloat(-90.0, 90.0) * 1000) / 1000.0,
115         'longitude': int(randfloat(-180.0, 180.0) * 1000) / 1000.0,
116         }
117             
118 def random_address_type():
119     return {
120         'name': randstr(20),
121         'description': randstr(254),
122         }
123
124 def random_address():
125     return {
126         'line1': randstr(254),
127         'line2': randstr(254),
128         'line3': randstr(254),
129         'city': randstr(254),
130         'state': randstr(254),
131         'postalcode': randstr(64),
132         'country': randstr(128),
133         }
134
135 def random_person(namelengths):
136     return {
137         'first_name': randstr(namelengths['first_name']),
138         'last_name': randstr(namelengths['last_name']),
139         'email': randemail(namelengths),
140         'bio': randstr(254),
141         # Accounts are disabled by default
142         'enabled': False,
143         'password': randstr(254),
144         }
145
146 def random_key(key_types,namelengths):
147     return {
148         'key_type': random.sample(key_types, 1)[0],
149         'key': randkey(namelengths)
150         }
151
152 def random_tag_type (role_ids):
153     return  {'tagname': randstr(12),
154              'category':randstr(8),
155              'min_role_id': random.sample(role_ids, 1)[0],
156              'description' : randstr(128),
157              }
158
159 def random_nodegroup():
160     return {'groupname' : randstr(50) }
161
162 tag_fields=['arch']
163 def random_node(node_types,boot_states,namelengths):
164     return {
165         'hostname': randhostname(namelengths),
166         'node_type': random.sample(node_types,1)[0],
167         'boot_state': random.sample(boot_states, 1)[0],
168         'model': randstr(namelengths['model']),
169         'version': randstr(64),
170         # for testing node tags
171         'arch':randstr(10),
172         }
173
174 def random_interface(method, type):
175     interface_fields = {
176         'method': method,
177         'type': type,
178         'bwlimit': randint(500000, 10000000),
179         }
180
181     if method != 'dhcp':
182         ip = randint(0, 0xffffffff)
183         netmask = (0xffffffff << randint(2, 31)) & 0xffffffff
184         network = ip & netmask
185         broadcast = ((ip & netmask) | ~netmask) & 0xffffffff
186         gateway = randint(network + 1, broadcast - 1)
187         dns1 = randint(0, 0xffffffff)
188
189         for field in 'ip', 'netmask', 'network', 'broadcast', 'gateway', 'dns1':
190             interface_fields[field] = socket.inet_ntoa(struct.pack('>L', locals()[field]))
191
192     return interface_fields
193
194 def random_ilink ():
195     return randstr (12)
196
197 def random_pcu(namelengths):
198     return {
199         'hostname': randhostname(namelengths),
200         'ip': socket.inet_ntoa(struct.pack('>L', randint(0, 0xffffffff))),
201         'protocol': randstr(16),
202         'username': randstr(254),
203         'password': randstr(254),
204         'notes': randstr(254),
205         'model': randstr(32),
206         }
207
208 def random_conf_file():
209     return {
210         'enabled': bool(randint()),
211         'source': randpath(255),
212         'dest': randpath(255),
213         'file_permissions': "%#o" % randint(0, 512),
214         'file_owner': randstr(32, letters + '_' + digits),
215         'file_group': randstr(32, letters + '_' + digits),
216         'preinstall_cmd': randpath(100),
217         'postinstall_cmd': randpath(100),
218         'error_cmd': randpath(100),
219         'ignore_cmd_errors': bool(randint()),
220         'always_update': bool(randint()),
221         }
222
223 def random_slice(login_base,namelengths):
224     return {
225         'name': login_base + "_" + randstr(11, letters).lower(),
226         'url': "http://" + randhostname(namelengths) + "/",
227         'description': randstr(2048),
228         }
229
230 class Test:
231     sizes_tiny = {
232         'sites': 1,
233         'address_types': 1,
234         'addresses_per_site': 1,
235         'persons_per_site': 1,
236         'keys_per_person': 1,
237         'slice_tags': 1,
238         'nodegroups': 1,
239         'nodes_per_site': 1,
240         'interfaces_per_node': 1,
241         'ilinks':1,
242         'pcus_per_site': 1,
243         'conf_files': 1,
244         'slices_per_site': 1,
245         'attributes_per_slice': 1,
246         }
247
248     sizes_default = {
249         'sites': 10,
250         'address_types': 2,
251         'addresses_per_site': 2,
252         'persons_per_site': 4,
253         'keys_per_person': 2,
254         'slice_tags': 10,
255         'nodegroups': 10,
256         'nodes_per_site': 2,
257         'interfaces_per_node': 1,
258         'ilinks': 20,
259         'pcus_per_site': 1,
260         'conf_files': 10,
261         'slices_per_site': 4,
262         'attributes_per_slice': 2,
263         }
264
265     namelengths_default = {
266         'hostname1': 61,
267         'hostname2':5,
268         'login_base':20,
269         'sitename':254,
270         'abbreviated_name':50,
271         'model':255,
272         'first_name':128,
273         'last_name':128,
274         'email':100,
275         }
276
277     namelengths_short = {
278         'hostname1': 12,
279         'hostname2':3,
280         'login_base':8,
281         'sitename':64,
282         'sitename_contents':letters+digits+whitespace+punctuation,
283         'abbreviated_name':24,
284         'abbreviated_name_contents':letters+digits+whitespace+punctuation,
285         'model':40,
286         'first_name':12,
287         'last_name':20,
288         'email':24,
289         }
290
291     def __init__(self, api, check = True, verbose = True, preserve = False):
292         self.api = api
293         self.check = check
294         self.verbose = verbose
295         self.preserve = preserve
296         
297         self.site_ids = []
298         self.address_type_ids = []
299         self.address_ids = []
300         self.person_ids = []
301         self.key_ids = []
302         self.slice_type_ids = []
303         self.nodegroup_type_ids = []
304         self.ilink_type_ids = []
305         self.nodegroup_ids = []
306         self.node_ids = []
307         self.interface_ids = []
308         self.ilink_ids = []
309         self.pcu_ids = []
310         self.conf_file_ids = []
311         self.slice_ids = []
312         self.slice_tag_ids = []
313
314     def Cardinals (self):
315         return [len(x) for x in ( 
316                 self.api.GetNodes({},['node_id']),
317                 self.api.GetSites({},['site_id']),
318                 self.api.GetPersons({},['person_id']),
319                 self.api.GetSlices({},['slice_id']),
320             )]
321
322     def Run(self, **kwds):
323         """
324         Run a complete database and API consistency test. Populates
325         the database with a set of random entities, updates them, then
326         deletes them. Examples:
327
328         test.Run() # Defaults
329         test.Run(**Test.sizes_default) # Defaults
330         test.Run(**Test.sizes_tiny) # Tiny set
331         test.Run(sites = 123, slices_per_site = 4) # Defaults with overrides
332         """
333
334         cardinals_before=self.Cardinals()
335         print 'Cardinals before test (n,s,p,sl)',cardinals_before
336
337         self.Add(**kwds)
338         self.Update()
339         if self.preserve:
340             print 'Preserving - delete skipped'
341         else:
342             self.Delete()
343
344             cardinals_after=self.Cardinals()
345             print 'Cardinals after test (n,s,p,sl)',cardinals_after
346
347             if cardinals_before != cardinals_after:
348                 raise Exception, 'cardinals before and after differ - check deletion mechanisms'
349
350     def Add(self, **kwds):
351         """
352         Populate the database with a set of random entities. Examples:
353
354         same args as Run()
355         """
356
357         sizes = self.sizes_default.copy()
358         sizes.update(kwds)
359
360         self.AddSites(sizes['sites'])
361         self.AddAddressTypes(sizes['address_types'])
362         self.AddAddresses(sizes['addresses_per_site'])
363         self.AddPersons(sizes['persons_per_site'])
364         self.AddKeys(sizes['keys_per_person'])
365         self.AddTagTypes(sizes['slice_tags'],sizes['nodegroups'],sizes['ilinks'])
366         self.AddNodeGroups(sizes['nodegroups'])
367         self.AddNodes(sizes['nodes_per_site'])
368         self.AddInterfaces(sizes['interfaces_per_node'])
369         self.AddIlinks (sizes['ilinks'])
370         self.AddPCUs(sizes['pcus_per_site'])
371         self.AddConfFiles(sizes['conf_files'])
372         self.AddSlices(sizes['slices_per_site'])
373         self.AddSliceTags(sizes['attributes_per_slice'])
374
375     def Update(self):
376         self.UpdateSites()
377         self.UpdateAddressTypes()
378         self.UpdateAddresses()
379         self.UpdatePersons()
380         self.UpdateKeys()
381         self.UpdateTagTypes()
382         self.UpdateNodeGroups()
383         self.UpdateNodes()
384         self.UpdateInterfaces()
385         self.UpdateIlinks()
386         self.UpdatePCUs()
387         self.UpdateConfFiles()
388         self.UpdateSlices()
389         self.UpdateSliceTags()
390
391     def Delete(self):
392         self.DeleteSliceTags()
393         self.DeleteSlices()
394         self.DeleteKeys()
395         self.DeleteConfFiles()
396         self.DeletePCUs()
397         self.DeleteIlinks()
398         self.DeleteInterfaces()
399         self.DeleteNodes()
400         self.DeletePersons()
401         self.DeleteNodeGroups()
402         self.DeleteTagTypes()
403         self.DeleteAddresses()
404         self.DeleteAddressTypes()
405         self.DeleteSites()
406
407     def AddSites(self, n = 10):
408         """
409         Add a number of random sites.
410         """
411
412         for i in range(n):
413             # Add site
414             site_fields = random_site(self.namelengths)
415             site_id = self.api.AddSite(site_fields)
416
417             # Should return a unique site_id
418             assert site_id not in self.site_ids
419             self.site_ids.append(site_id)
420
421             # Enable slice creation
422             site_fields['max_slices'] = randint(1, 10)
423             self.api.UpdateSite(site_id, site_fields)
424
425             if self.check:
426                 # Check site
427                 site = self.api.GetSites([site_id])[0]
428                 for field in site_fields:
429                     assert site[field] == site_fields[field]
430
431             if self.verbose:
432                 print "Added site", site_id
433
434     def UpdateSites(self):
435         """
436         Make random changes to any sites we may have added.
437         """
438
439         for site_id in self.site_ids:
440             # Update site
441             site_fields = random_site(self.namelengths)
442             # Do not change login_base
443             if 'login_base' in site_fields:
444                 del site_fields['login_base']
445             self.api.UpdateSite(site_id, site_fields)
446
447             if self.check:
448                 # Check site
449                 site = self.api.GetSites([site_id])[0]
450                 for field in site_fields:
451                     assert site[field] == site_fields[field]
452
453             if self.verbose:
454                 print "Updated site", site_id
455
456     def DeleteSites(self):
457         """
458         Delete any random sites we may have added.
459         """
460
461         for site_id in self.site_ids:
462             self.api.DeleteSite(site_id)
463
464             if self.check:
465                 assert not self.api.GetSites([site_id])
466
467             if self.verbose:
468                 print "Deleted site", site_id
469
470         if self.check:
471             assert not self.api.GetSites(self.site_ids)
472
473         self.site_ids = []
474
475     def AddAddressTypes(self, n = 2):
476         """
477         Add a number of random address types.
478         """
479
480         for i in range(n):
481             address_type_fields = random_address_type()
482             address_type_id = self.api.AddAddressType(address_type_fields)
483
484             # Should return a unique address_type_id
485             assert address_type_id not in self.address_type_ids
486             self.address_type_ids.append(address_type_id)
487
488             if self.check:
489                 # Check address type
490                 address_type = self.api.GetAddressTypes([address_type_id])[0]
491                 for field in address_type_fields:
492                     assert address_type[field] == address_type_fields[field]
493
494             if self.verbose:
495                 print "Added address type", address_type_id
496
497     def UpdateAddressTypes(self):
498         """
499         Make random changes to any address types we may have added.
500         """
501
502         for address_type_id in self.address_type_ids:
503             # Update address_type
504             address_type_fields = random_address_type()
505             self.api.UpdateAddressType(address_type_id, address_type_fields)
506
507             if self.check:
508                 # Check address type
509                 address_type = self.api.GetAddressTypes([address_type_id])[0]
510                 for field in address_type_fields:
511                     assert address_type[field] == address_type_fields[field]
512
513             if self.verbose:
514                 print "Updated address_type", address_type_id
515
516     def DeleteAddressTypes(self):
517         """
518         Delete any random address types we may have added.
519         """
520
521         for address_type_id in self.address_type_ids:
522             self.api.DeleteAddressType(address_type_id)
523
524             if self.check:
525                 assert not self.api.GetAddressTypes([address_type_id])
526
527             if self.verbose:
528                 print "Deleted address type", address_type_id
529
530         if self.check:
531             assert not self.api.GetAddressTypes(self.address_type_ids)
532
533         self.address_type_ids = []
534
535     def AddAddresses(self, per_site = 2):
536         """
537         Add a number of random addresses to each site.
538         """
539
540         for site_id in self.site_ids:
541             for i in range(per_site):
542                 address_fields = random_address()
543                 address_id = self.api.AddSiteAddress(site_id, address_fields)
544
545                 # Should return a unique address_id
546                 assert address_id not in self.address_ids
547                 self.address_ids.append(address_id)
548
549                 # Add random address type
550                 if self.address_type_ids:
551                     for address_type_id in random.sample(self.address_type_ids, 1):
552                         self.api.AddAddressTypeToAddress(address_type_id, address_id)
553
554                 if self.check:
555                     # Check address
556                     address = self.api.GetAddresses([address_id])[0]
557                     for field in address_fields:
558                         assert address[field] == address_fields[field]
559
560                 if self.verbose:
561                     print "Added address", address_id, "to site", site_id
562
563     def UpdateAddresses(self):
564         """
565         Make random changes to any addresses we may have added.
566         """
567
568         for address_id in self.address_ids:
569             # Update address
570             address_fields = random_address()
571             self.api.UpdateAddress(address_id, address_fields)
572
573             if self.check:
574                 # Check address
575                 address = self.api.GetAddresses([address_id])[0]
576                 for field in address_fields:
577                     assert address[field] == address_fields[field]
578
579             if self.verbose:
580                 print "Updated address", address_id
581
582     def DeleteAddresses(self):
583         """
584         Delete any random addresses we may have added.
585         """
586
587         for address_id in self.address_ids:
588             # Remove address types
589             address = self.api.GetAddresses([address_id])[0]
590             for address_type_id in address['address_type_ids']:
591                 self.api.DeleteAddressTypeFromAddress(address_type_id, address_id)
592
593             if self.check:
594                 address = self.api.GetAddresses([address_id])[0]
595                 assert not address['address_type_ids']
596
597             self.api.DeleteAddress(address_id)
598
599             if self.check:
600                 assert not self.api.GetAddresses([address_id])
601
602             if self.verbose:
603                 print "Deleted address", address_id
604
605         if self.check:
606             assert not self.api.GetAddresses(self.address_ids)
607
608         self.address_ids = []
609
610     def AddPersons(self, per_site = 10):
611         """
612         Add a number of random users to each site.
613         """
614
615         for site_id in self.site_ids:
616             for i in range(per_site):
617                 # Add user
618                 person_fields = random_person(self.namelengths)
619                 person_id = self.api.AddPerson(person_fields)
620
621                 # Should return a unique person_id
622                 assert person_id not in self.person_ids
623                 self.person_ids.append(person_id)
624
625                 if self.check:
626                     # Check user
627                     person = self.api.GetPersons([person_id])[0]
628                     for field in person_fields:
629                         if field != 'password':
630                             assert person[field] == person_fields[field]
631
632                 auth = {'AuthMethod': "password",
633                         'Username': person_fields['email'],
634                         'AuthString': person_fields['password']}
635
636                 if self.check:
637                     # Check that user is disabled
638                     try:
639                         assert not self.api.AuthCheck(auth)
640                     except:
641                         pass
642
643                 # Add random set of roles
644                 role_ids = random.sample([20, 30, 40], randint(1, 3))
645                 for role_id in role_ids:
646                     self.api.AddRoleToPerson(role_id, person_id)
647
648                 if self.check:
649                     person = self.api.GetPersons([person_id])[0]
650                     assert set(role_ids) == set(person['role_ids'])
651
652                 # Enable user
653                 self.api.UpdatePerson(person_id, {'enabled': True})
654
655                 if self.check:
656                     # Check that user is enabled
657                     assert self.api.AuthCheck(auth)
658
659                 # Associate user with site
660                 self.api.AddPersonToSite(person_id, site_id)
661                 self.api.SetPersonPrimarySite(person_id, site_id)
662
663                 if self.check:
664                     person = self.api.GetPersons([person_id])[0]
665                     assert person['site_ids'][0] == site_id
666
667                 if self.verbose:
668                     print "Added user", person_id, "to site", site_id
669
670     def UpdatePersons(self):
671         """
672         Make random changes to any users we may have added.
673         """
674
675         for person_id in self.person_ids:
676             # Update user
677             person_fields = random_person(self.namelengths)
678             # Keep them enabled
679             person_fields['enabled'] = True
680             self.api.UpdatePerson(person_id, person_fields)
681
682             if self.check:
683                 # Check user
684                 person = self.api.GetPersons([person_id])[0]
685                 for field in person_fields:
686                     if field != 'password':
687                         assert person[field] == person_fields[field]
688
689             if self.verbose:
690                 print "Updated person", person_id
691
692             person = self.api.GetPersons([person_id])[0]
693
694             # Associate user with a random set of sites
695             site_ids = random.sample(self.site_ids, randint(0, len(self.site_ids)))
696             for site_id in (set(site_ids) - set(person['site_ids'])):
697                 self.api.AddPersonToSite(person_id, site_id)
698             for site_id in (set(person['site_ids']) - set(site_ids)):
699                 self.api.DeletePersonFromSite(person_id, site_id)
700
701             if site_ids:
702                 self.api.SetPersonPrimarySite(person_id, site_ids[0])
703
704             if self.check:
705                 person = self.api.GetPersons([person_id])[0]
706                 assert set(site_ids) == set(person['site_ids'])
707
708             if self.verbose:
709                 print "Updated person", person_id, "to sites", site_ids
710
711     def DeletePersons(self):
712         """
713         Delete any random users we may have added.
714         """
715
716         for person_id in self.person_ids:
717             # Remove from site
718             person = self.api.GetPersons([person_id])[0]
719             for site_id in person['site_ids']:
720                 self.api.DeletePersonFromSite(person_id, site_id)
721
722             if self.check:
723                 person = self.api.GetPersons([person_id])[0]
724                 assert not person['site_ids']
725
726             # Revoke roles
727             for role_id in person['role_ids']:
728                 self.api.DeleteRoleFromPerson(role_id, person_id)
729
730             if self.check:
731                 person = self.api.GetPersons([person_id])[0]
732                 assert not person['role_ids']
733
734             # Disable account
735             self.api.UpdatePerson(person_id, {'enabled': False})
736
737             if self.check:
738                 person = self.api.GetPersons([person_id])[0]
739                 assert not person['enabled']
740
741             # Delete account
742             self.api.DeletePerson(person_id)
743
744             if self.check:
745                 assert not self.api.GetPersons([person_id])                         
746
747             if self.verbose:
748                 print "Deleted user", person_id
749
750         if self.check:
751             assert not self.api.GetPersons(self.person_ids)
752
753         self.person_ids = []
754
755     def AddKeys(self, per_person = 2):
756         """
757         Add a number of random keys to each user.
758         """
759
760         key_types = self.api.GetKeyTypes()
761         if not key_types:
762             raise Exception, "No key types"
763
764         for person_id in self.person_ids:
765             for i in range(per_person):
766                 # Add key
767                 key_fields = random_key(key_types,self.namelengths)
768                 key_id = self.api.AddPersonKey(person_id, key_fields)
769
770                 # Should return a unique key_id
771                 assert key_id not in self.key_ids
772                 self.key_ids.append(key_id)
773
774                 if self.check:
775                     # Check key
776                     key = self.api.GetKeys([key_id])[0]
777                     for field in key_fields:
778                         assert key[field] == key_fields[field]
779
780                     # Add and immediately blacklist a key
781                     key_fields = random_key(key_types,self.namelengths)
782                     key_id = self.api.AddPersonKey(person_id, key_fields)
783
784                     self.api.BlacklistKey(key_id)
785
786                     # Is effectively deleted
787                     assert not self.api.GetKeys([key_id])
788
789                     # Cannot be added again
790                     try:
791                         key_id = self.api.AddPersonKey(person_id, key_fields)
792                         assert False
793                     except Exception, e:
794                         pass
795
796                 if self.verbose:
797                     print "Added key", key_id, "to user", person_id
798
799     def UpdateKeys(self):
800         """
801         Make random changes to any keys we may have added.
802         """
803
804         key_types = self.api.GetKeyTypes()
805         if not key_types:
806             raise Exception, "No key types"
807
808         for key_id in self.key_ids:
809             # Update key
810             key_fields = random_key(key_types,self.namelengths)
811             self.api.UpdateKey(key_id, key_fields)
812
813             if self.check:
814                 # Check key
815                 key = self.api.GetKeys([key_id])[0]
816                 for field in key_fields:
817                     assert key[field] == key_fields[field]
818
819             if self.verbose:
820                 print "Updated key", key_id
821
822     def DeleteKeys(self):
823         """
824         Delete any random keys we may have added.
825         """
826
827         for key_id in self.key_ids:
828             self.api.DeleteKey(key_id)
829
830             if self.check:
831                 assert not self.api.GetKeys([key_id])
832
833             if self.verbose:
834                 print "Deleted key", key_id
835
836         if self.check:
837             assert not self.api.GetKeys(self.key_ids)
838
839         self.key_ids = []
840
841     def AddNodeGroups(self, n = 10):
842         """
843         Add a number of random node groups.
844         """
845
846         for i in range(n):
847             # locate tag type
848             tag_type_id = self.nodegroup_type_ids[i]
849             tagname=self.api.GetTagTypes([tag_type_id])[0]['tagname']
850             
851             # Add node group
852             groupname = random_nodegroup() ['groupname']
853             value = 'yes'
854             nodegroup_id = self.api.AddNodeGroup(groupname, tagname, value)
855
856             # Should return a unique nodegroup_id
857             assert nodegroup_id not in self.nodegroup_ids
858             self.nodegroup_ids.append(nodegroup_id)
859
860             if self.check:
861                 # Check node group
862                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
863                 assert nodegroup['groupname'] == groupname
864                 assert nodegroup['tagname'] == tagname
865                 assert nodegroup['value'] == value
866
867             if self.verbose:
868                 print "Added node group", nodegroup_id
869
870     def UpdateNodeGroups(self):
871         """
872         Make random changes to any node groups we may have added.
873         """
874
875         for nodegroup_id in self.nodegroup_ids:
876             # Update nodegroup
877             groupname = random_nodegroup()['groupname']
878             # cannot change tagname
879             nodegroup_fields = { 'groupname':groupname }
880             self.api.UpdateNodeGroup(nodegroup_id, nodegroup_fields)
881
882             if self.check:
883                 # Check nodegroup
884                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
885                 for field in nodegroup_fields:
886                     assert nodegroup[field] == nodegroup_fields[field]
887
888             if self.verbose:
889                 print "Updated node group", nodegroup_id
890
891     def DeleteNodeGroups(self):
892         """
893         Delete any random node groups we may have added.
894         """
895
896         for nodegroup_id in self.nodegroup_ids:
897             self.api.DeleteNodeGroup(nodegroup_id)
898
899             if self.check:
900                 assert not self.api.GetNodeGroups([nodegroup_id])
901
902             if self.verbose:
903                 print "Deleted node group", nodegroup_id
904
905         if self.check:
906             assert not self.api.GetNodeGroups(self.nodegroup_ids)
907
908         self.nodegroup_ids = []
909
910     def AddNodes(self, per_site = 2):
911         """
912         Add a number of random nodes to each site. Each node will also
913         be added to a random node group if AddNodeGroups() was
914         previously run.
915         """
916         
917         node_types = self.api.GetNodeTypes()
918         if not node_types:
919             raise Exception, "No node types"
920         boot_states = self.api.GetBootStates()
921         if not boot_states:
922             raise Exception, "No boot states"
923
924         for site_id in self.site_ids:
925             for i in range(per_site):
926                 # Add node
927                 node_fields = random_node(node_types,boot_states,self.namelengths)
928                 node_id = self.api.AddNode(site_id, node_fields)
929
930                 # Should return a unique node_id
931                 assert node_id not in self.node_ids
932                 self.node_ids.append(node_id)
933
934                 # Add to a random set of node groups
935                 nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
936                 for nodegroup_id in nodegroup_ids:
937                     tagname = self.api.GetNodeGroups([nodegroup_id])[0]['tagname']
938                     self.api.AddNodeTag( node_id, tagname, 'yes' )
939
940                 if self.check:
941                     # Check node
942                     node = self.api.GetNodes([node_id])[0]
943                     for field in node_fields:
944                         if field not in tag_fields:
945                             assert node[field] == node_fields[field]
946
947                 if self.verbose:
948                     print "Added node", node_id
949
950     def UpdateNodes(self):
951         """
952         Make random changes to any nodes we may have added.
953         """
954
955         node_types = self.api.GetNodeTypes()
956         if not node_types:
957             raise Exception, "No node types"
958         boot_states = self.api.GetBootStates()
959         if not boot_states:
960             raise Exception, "No boot states"
961
962         for node_id in self.node_ids:
963             # Update node
964             node_fields = random_node(node_types,boot_states,self.namelengths)
965             self.api.UpdateNode(node_id, node_fields)
966             
967             node = self.api.GetNodes([node_id])[0]
968
969             # Add to a random set of node groups
970             nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
971             for nodegroup_id in (set(nodegroup_ids) - set(node['nodegroup_ids'])):
972                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
973                 tagname = nodegroup['tagname']
974                 node_tags = self.api.GetNodeTags({'node_id':node_id,'tagname':tagname})
975                 if not node_tags:
976                     self.api.AddNodeTag(node_id,tagname,'yes')
977                 else:
978                     node_tag=node_tags[0]
979                     self.api.UpdateNodeTag(node_tag['node_tag_id'],'yes')
980             for nodegroup_id in (set(node['nodegroup_ids']) - set(nodegroup_ids)):
981                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
982                 tagname = nodegroup['tagname']
983                 node_tags = self.api.GetNodeTags({'node_id':node_id,'tagname':tagname})
984                 if not node_tags:
985                     self.api.AddNodeTag(node_id,tagname,'no')
986                 else:
987                     node_tag=node_tags[0]
988                     self.api.UpdateNodeTag(node_tag['node_tag_id'],'no')
989
990             if self.check:
991                 # Check node
992                 node = self.api.GetNodes([node_id])[0]
993                 for field in node_fields:
994                     if field not in tag_fields:
995                         if node[field] != node_fields[field]:
996                             raise Exception, "Unexpected field %s in node after GetNodes()"%field
997                 assert set(nodegroup_ids) == set(node['nodegroup_ids'])
998
999                 print 'WARNING: skipping updatenode with tags as this is not implemented yet'
1000                 # again when fetching 'arch' explicitly
1001                 node2 = self.api.GetNodes([node_id],node_fields.keys())[0]
1002                 for field in node_fields:
1003                     if node2[field] != node_fields[field]:
1004                         raise Exception, "Unexpected field %s in node after GetNodes(tags)"%field
1005
1006             if self.verbose:
1007                 print "Updated node", node_id
1008
1009     def DeleteNodes(self):
1010         """
1011         Delete any random nodes we may have added.
1012         """
1013
1014         for node_id in self.node_ids:
1015             # Remove from node groups
1016             node = self.api.GetNodes([node_id])[0]
1017             for node_tag in GetNodeTags ( {'node_id': node_id} ):
1018                 self.api.UpdateNodeTag(node_tag['node_tag_id'],'')
1019
1020             if self.check:
1021                 node = self.api.GetNodes([node_id])[0]
1022                 assert not node['nodegroup_ids']
1023
1024             self.api.DeleteNode(node_id)
1025
1026             if self.check:
1027                 assert not self.api.GetNodes([node_id])
1028
1029             if self.verbose:
1030                 print "Deleted node", node_id
1031
1032         if self.check:
1033             assert not self.api.GetNodes(self.node_ids)
1034
1035         self.node_ids = []
1036
1037     def AddInterfaces(self, per_node = 1):
1038         """
1039         Add a number of random network interfaces to each node.
1040         """
1041
1042         network_methods = self.api.GetNetworkMethods()
1043         if not network_methods:
1044             raise Exception, "No network methods"
1045         
1046         network_types = self.api.GetNetworkTypes()
1047         if not network_types:
1048             raise Exception, "No network types"
1049
1050         for node_id in self.node_ids:
1051             for i in range(per_node):
1052                 method = random.sample(network_methods, 1)[0]
1053                 type = random.sample(network_types, 1)[0]
1054
1055                 # Add interface
1056                 interface_fields = random_interface(method, type)
1057                 interface_id = self.api.AddInterface(node_id, interface_fields)
1058
1059                 # Should return a unique interface_id
1060                 assert interface_id not in self.interface_ids
1061                 self.interface_ids.append(interface_id)
1062
1063                 if self.check:
1064                     # Check interface
1065                     interface = self.api.GetInterfaces([interface_id])[0]
1066                     for field in interface_fields:
1067                         assert interface[field] == interface_fields[field]
1068
1069                 if self.verbose:
1070                     print "Added interface", interface_id, "to node", node_id
1071
1072     def UpdateInterfaces(self):
1073         """
1074         Make random changes to any network interfaces we may have added.
1075         """
1076
1077         network_methods = self.api.GetNetworkMethods()
1078         if not network_methods:
1079             raise Exception, "No network methods"
1080         
1081         network_types = self.api.GetNetworkTypes()
1082         if not network_types:
1083             raise Exception, "No network types"
1084
1085         for interface_id in self.interface_ids:
1086             method = random.sample(network_methods, 1)[0]
1087             type = random.sample(network_types, 1)[0]
1088
1089             # Update interface
1090             interface_fields = random_interface(method, type)
1091             self.api.UpdateInterface(interface_id, interface_fields)
1092
1093             if self.check:
1094                 # Check interface
1095                 interface = self.api.GetInterfaces([interface_id])[0]
1096                 for field in interface_fields:
1097                     assert interface[field] == interface_fields[field]
1098
1099             if self.verbose:
1100                 print "Updated interface", interface_id
1101
1102     def DeleteInterfaces(self):
1103         """
1104         Delete any random network interfaces we may have added.
1105         """
1106
1107         for interface_id in self.interface_ids:
1108             self.api.DeleteInterface(interface_id)
1109
1110             if self.check:
1111                 assert not self.api.GetInterfaces([interface_id])
1112
1113             if self.verbose:
1114                 print "Deleted interface", interface_id
1115
1116         if self.check:
1117             assert not self.api.GetInterfaces(self.interface_ids)
1118
1119         self.interface_ids = []
1120         
1121     def AddIlinks (self, n):
1122         """
1123         Add random links between interfaces.
1124         """
1125
1126         for i in range (n):
1127             src = random.sample(self.interface_ids,1)[0]
1128             dst = random.sample(self.interface_ids,1)[0]
1129             ilink_id = self.api.AddIlink (src,dst,
1130                                           self.ilink_type_ids[i],
1131                                           random_ilink())
1132
1133             assert ilink_id not in self.ilink_ids
1134             self.ilink_ids.append(ilink_id)
1135
1136             if self.verbose:
1137                 print 'Added Ilink',ilink_id,' - attached interface',src,'to',dst
1138
1139             if self.check:
1140                 retrieve=GetIlinks({'src_interface_id':src,'dst_interface_id':dst,
1141                                     'tag_type_id':self.ilink_type_ids[i]})
1142                 assert ilink_id==retrieve[0]['ilink_id']
1143
1144
1145     def UpdateIlinks (self):
1146
1147         for ilink_id in self.ilink_ids:
1148             new_value=random_ilink()
1149             self.api.UpdateIlink(ilink_id,new_value)
1150
1151             if self.check:
1152                 ilink=self.api.GetIlinks([ilink_id])[0]
1153                 assert ilink['value'] == new_value
1154
1155             if self.verbose:
1156                 print 'Updated Ilink',ilink_id
1157
1158     def DeleteIlinks (self):
1159         for ilink_id in self.ilink_ids:
1160             self.api.DeleteIlink(ilink_id)
1161
1162             if self.check:
1163                 assert not self.api.GetIlinks({'ilink_id':ilink_id})
1164
1165             if self.verbose:
1166                 print 'Deleted Ilink',ilink_id
1167
1168         if self.check:
1169             assert not self.api.GetIlinks(self.ilink_ids)
1170
1171         self.ilink_ids = []
1172
1173
1174     def AddPCUs(self, per_site = 1):
1175         """
1176         Add a number of random PCUs to each site. Each node at the
1177         site will be added to a port on the PCU if AddNodes() was
1178         previously run.
1179         """
1180
1181         for site_id in self.site_ids:
1182             for i in range(per_site):
1183                 # Add PCU
1184                 pcu_fields = random_pcu(self.namelengths)
1185                 pcu_id = self.api.AddPCU(site_id, pcu_fields)
1186
1187                 # Should return a unique pcu_id
1188                 assert pcu_id not in self.pcu_ids
1189                 self.pcu_ids.append(pcu_id)
1190
1191                 # Add each node at this site to a different port on this PCU
1192                 site = self.api.GetSites([site_id])[0]
1193                 port = randint(1, 10)
1194                 for node_id in site['node_ids']:
1195                     self.api.AddNodeToPCU(node_id, pcu_id, port)
1196                     port += 1
1197
1198                 if self.check:
1199                     # Check PCU
1200                     pcu = self.api.GetPCUs([pcu_id])[0]
1201                     for field in pcu_fields:
1202                         assert pcu[field] == pcu_fields[field]
1203
1204                 if self.verbose:
1205                     print "Added PCU", pcu_id, "to site", site_id
1206
1207     def UpdatePCUs(self):
1208         """
1209         Make random changes to any PCUs we may have added.
1210         """
1211
1212         for pcu_id in self.pcu_ids:
1213             # Update PCU
1214             pcu_fields = random_pcu(self.namelengths)
1215             self.api.UpdatePCU(pcu_id, pcu_fields)
1216
1217             if self.check:
1218                 # Check PCU
1219                 pcu = self.api.GetPCUs([pcu_id])[0]
1220                 for field in pcu_fields:
1221                     assert pcu[field] == pcu_fields[field]
1222
1223             if self.verbose:
1224                 print "Updated PCU", pcu_id
1225
1226     def DeletePCUs(self):
1227         """
1228         Delete any random nodes we may have added.
1229         """
1230
1231         for pcu_id in self.pcu_ids:
1232             # Remove nodes from PCU
1233             pcu = self.api.GetPCUs([pcu_id])[0]
1234             for node_id in pcu['node_ids']:
1235                 self.api.DeleteNodeFromPCU(node_id, pcu_id)
1236
1237             if self.check:
1238                 pcu = self.api.GetPCUs([pcu_id])[0]
1239                 assert not pcu['node_ids']
1240
1241             self.api.DeletePCU(pcu_id)
1242
1243             if self.check:
1244                 assert not self.api.GetPCUs([pcu_id])
1245
1246             if self.verbose:
1247                 print "Deleted PCU", pcu_id
1248
1249         if self.check:
1250             assert not self.api.GetPCUs(self.pcu_ids)
1251
1252         self.pcu_ids = []
1253
1254     def AddConfFiles(self, n = 10):
1255         """
1256         Add a number of random global configuration files.
1257         """
1258
1259         conf_files = []
1260
1261         for i in range(n):
1262             # Add a random configuration file
1263             conf_files.append(random_conf_file())
1264
1265         if n:
1266             # Add a nodegroup override file
1267             nodegroup_conf_file = conf_files[0].copy()
1268             nodegroup_conf_file['source'] = randpath(255)
1269             conf_files.append(nodegroup_conf_file)
1270
1271             # Add a node override file
1272             node_conf_file = conf_files[0].copy()
1273             node_conf_file['source'] = randpath(255)
1274             conf_files.append(node_conf_file)
1275
1276         for conf_file_fields in conf_files:
1277             conf_file_id = self.api.AddConfFile(conf_file_fields)
1278
1279             # Should return a unique conf_file_id
1280             assert conf_file_id not in self.conf_file_ids
1281             self.conf_file_ids.append(conf_file_id)
1282
1283             # Add to nodegroup
1284             if conf_file_fields == nodegroup_conf_file and self.nodegroup_ids:
1285                 nodegroup_id = random.sample(self.nodegroup_ids, 1)[0]
1286                 self.api.AddConfFileToNodeGroup(conf_file_id, nodegroup_id)
1287             else:
1288                 nodegroup_id = None
1289
1290             # Add to node
1291             if conf_file_fields == node_conf_file and self.node_ids:
1292                 node_id = random.sample(self.node_ids, 1)[0]
1293                 self.api.AddConfFileToNode(conf_file_id, node_id)
1294             else:
1295                 node_id = None
1296
1297             if self.check:
1298                 # Check configuration file
1299                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1300                 for field in conf_file_fields:
1301                     assert conf_file[field] == conf_file_fields[field]
1302
1303             if self.verbose:
1304                 print "Added configuration file", conf_file_id,
1305                 if nodegroup_id is not None:
1306                     print "to node group", nodegroup_id,
1307                 elif node_id is not None:
1308                     print "to node", node_id,
1309                 print
1310
1311     def UpdateConfFiles(self):
1312         """
1313         Make random changes to any configuration files we may have added.
1314         """
1315
1316         for conf_file_id in self.conf_file_ids:
1317             # Update configuration file
1318             conf_file_fields = random_conf_file()
1319             # Do not update dest so that it remains an override if set
1320             if 'dest' in conf_file_fields:
1321                 del conf_file_fields['dest']
1322             self.api.UpdateConfFile(conf_file_id, conf_file_fields)
1323
1324             if self.check:
1325                 # Check configuration file
1326                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1327                 for field in conf_file_fields:
1328                     assert conf_file[field] == conf_file_fields[field]
1329
1330             if self.verbose:
1331                 print "Updated configuration file", conf_file_id
1332
1333     def DeleteConfFiles(self):
1334         """
1335         Delete any random configuration files we may have added.
1336         """
1337
1338         for conf_file_id in self.conf_file_ids:
1339             self.api.DeleteConfFile(conf_file_id)
1340
1341             if self.check:
1342                 assert not self.api.GetConfFiles([conf_file_id])
1343
1344             if self.verbose:
1345                 print "Deleted configuration file", conf_file_id
1346
1347         if self.check:
1348             assert not self.api.GetConfFiles(self.conf_file_ids)
1349
1350         self.conf_file_ids = []
1351
1352     def AddTagTypes(self,n_sa,n_ng,n_il):
1353         """
1354         Add as many tag types as there are nodegroups, 
1355         will use value=yes for each nodegroup
1356         """
1357
1358         roles = self.api.GetRoles()
1359         if not roles:
1360             raise Exception, "No roles"
1361         role_ids = [role['role_id'] for role in roles]
1362
1363         for i in range (n_sa + n_ng + n_il):
1364             tag_type_fields = random_tag_type (role_ids)
1365             tag_type_id = self.api.AddTagType (tag_type_fields)
1366
1367             assert tag_type_id not in \
1368                 self.slice_type_ids + \
1369                 self.nodegroup_type_ids + \
1370                 self.ilink_type_ids
1371             
1372             if i < n_sa:
1373                 self.slice_type_ids.append(tag_type_id)
1374             elif i < n_sa+n_ng :
1375                 self.nodegroup_type_ids.append(tag_type_id)
1376             else:
1377                 self.ilink_type_ids.append(tag_type_id)
1378
1379             if self.check:
1380                 tag_type = self.api.GetTagTypes([tag_type_id])[0]
1381                 for field in tag_type_fields:
1382                     assert tag_type[field] == tag_type_fields[field]
1383             if self.verbose:
1384                 print "Updated slice attribute type", tag_type_id
1385
1386     def UpdateTagTypes(self):
1387         """
1388         Make random changes to any slice attribute types we may have added.
1389         """
1390
1391         roles = self.api.GetRoles()
1392         if not roles:
1393             raise Exception, "No roles"
1394         role_ids = [role['role_id'] for role in roles]
1395
1396         for tag_type_id in self.slice_type_ids + self.nodegroup_type_ids + self.ilink_type_ids:
1397             # Update slice attribute type
1398             tag_type_fields = random_tag_type(role_ids)
1399             self.api.UpdateTagType(tag_type_id, tag_type_fields)
1400
1401             if self.check:
1402                 # Check slice attribute type
1403                 tag_type = self.api.GetTagTypes([tag_type_id])[0]
1404                 for field in tag_type_fields:
1405                     assert tag_type[field] == tag_type_fields[field]
1406             if self.verbose:
1407                 print "Updated slice attribute type", tag_type_id
1408
1409     def DeleteTagTypes(self):
1410         """
1411         Delete any random slice attribute types we may have added.
1412         """
1413
1414         for tag_type_id in self.slice_type_ids + self.nodegroup_type_ids + self.ilink_type_ids:
1415             self.api.DeleteTagType(tag_type_id)
1416
1417             if self.check:
1418                 assert not self.api.GetTagTypes([tag_type_id])
1419
1420             if self.verbose:
1421                 print "Deleted slice attribute type", tag_type_id
1422
1423         if self.check:
1424             assert not self.api.GetTagTypes(self.slice_type_ids+self.nodegroup_type_ids+self.ilink_type_ids)
1425
1426         self.slice_type_ids = []
1427         self.nodegroup_type_ids = []
1428
1429     def AddSlices(self, per_site = 10):
1430         """
1431         Add a number of random slices per site.
1432         """
1433
1434         for site in self.api.GetSites(self.site_ids):
1435             for i in range(min(per_site, site['max_slices'])):
1436                 # Add slice
1437                 slice_fields = random_slice(site['login_base'],self.namelengths)
1438                 slice_id = self.api.AddSlice(slice_fields)
1439
1440                 # Should return a unique slice_id
1441                 assert slice_id not in self.slice_ids
1442                 self.slice_ids.append(slice_id)
1443
1444                 # Add slice to a random set of nodes
1445                 node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1446                 if node_ids:
1447                     self.api.AddSliceToNodes(slice_id, node_ids)
1448
1449                 # Add random set of site users to slice
1450                 person_ids = random.sample(site['person_ids'], randint(0, len(site['person_ids'])))
1451                 for person_id in person_ids:
1452                     self.api.AddPersonToSlice(person_id, slice_id)
1453
1454                 if self.check:
1455                     # Check slice
1456                     slice = self.api.GetSlices([slice_id])[0]
1457                     for field in slice_fields:
1458                         assert slice[field] == slice_fields[field]
1459
1460                     assert set(node_ids) == set(slice['node_ids'])
1461                     assert set(person_ids) == set(slice['person_ids'])
1462
1463                 if self.verbose:
1464                     print "Added slice", slice_id, "to site", site['site_id'],
1465                     if node_ids:
1466                         print "and nodes", node_ids,
1467                     print
1468                     if person_ids:
1469                         print "Added users", site['person_ids'], "to slice", slice_id
1470
1471     def UpdateSlices(self):
1472         """
1473         Make random changes to any slices we may have added.
1474         """
1475
1476         for slice_id in self.slice_ids:
1477             # Update slice
1478             slice_fields = random_slice("unused",self.namelengths)
1479             # Cannot change slice name
1480             if 'name' in slice_fields:
1481                 del slice_fields['name']
1482             self.api.UpdateSlice(slice_id, slice_fields)
1483
1484             slice = self.api.GetSlices([slice_id])[0]
1485
1486             # Add slice to a random set of nodes
1487             node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1488             self.api.AddSliceToNodes(slice_id, list(set(node_ids) - set(slice['node_ids'])))
1489             self.api.DeleteSliceFromNodes(slice_id, list(set(slice['node_ids']) - set(node_ids)))
1490
1491             # Add random set of users to slice
1492             person_ids = random.sample(self.person_ids, randint(0, len(self.person_ids)))
1493             for person_id in (set(person_ids) - set(slice['person_ids'])):
1494                 self.api.AddPersonToSlice(person_id, slice_id)
1495             for person_id in (set(slice['person_ids']) - set(person_ids)):
1496                 self.api.DeletePersonFromSlice(person_id, slice_id)
1497
1498             if self.check:
1499                 slice = self.api.GetSlices([slice_id])[0]
1500                 for field in slice_fields:
1501                     assert slice[field] == slice_fields[field]
1502                 assert set(node_ids) == set(slice['node_ids'])
1503                 assert set(person_ids) == set(slice['person_ids'])
1504
1505             if self.verbose:
1506                 print "Updated slice", slice_id
1507                 print "Added nodes", node_ids, "to slice", slice_id
1508                 print "Added persons", person_ids, "to slice", slice_id
1509
1510     def DeleteSlices(self):
1511         """
1512         Delete any random slices we may have added.
1513         """
1514
1515         for slice_id in self.slice_ids:
1516             self.api.DeleteSlice(slice_id)
1517
1518             if self.check:
1519                 assert not self.api.GetSlices([slice_id])
1520
1521             if self.verbose:
1522                 print "Deleted slice", slice_id
1523
1524         if self.check:
1525             assert not self.api.GetSlices(self.slice_ids)
1526
1527         self.slice_ids = []
1528
1529     def AddSliceTags(self, per_slice = 2):
1530         """
1531         Add a number of random slices per site.
1532         """
1533
1534         if not self.slice_type_ids:
1535             return
1536
1537         for slice_id in self.slice_ids:
1538             slice = self.api.GetSlices([slice_id])[0]
1539
1540             for i in range(per_slice):
1541                 # Set a random slice/sliver attribute
1542                 for tag_type_id in random.sample(self.slice_type_ids, 1):
1543                     value = randstr(16, letters + '_' + digits)
1544                     # Make it a sliver attribute with 50% probability
1545                     if slice['node_ids']:
1546                         node_id = random.sample(slice['node_ids'] + [None] * len(slice['node_ids']), 1)[0]
1547                     else:
1548                         node_id = None
1549
1550                     # Add slice attribute
1551                     if node_id is None:
1552                         slice_tag_id = self.api.AddSliceTag(slice_id, tag_type_id, value)
1553                     else:
1554                         slice_tag_id = self.api.AddSliceTag(slice_id, tag_type_id, value, node_id)
1555
1556                     # Should return a unique slice_tag_id
1557                     assert slice_tag_id not in self.slice_tag_ids
1558                     self.slice_tag_ids.append(slice_tag_id)
1559
1560                     if self.check:
1561                         # Check slice attribute
1562                         slice_tag = self.api.GetSliceTags([slice_tag_id])[0]
1563                         for field in 'tag_type_id', 'slice_id', 'node_id', 'slice_tag_id', 'value':
1564                             assert slice_tag[field] == locals()[field]
1565
1566                     if self.verbose:
1567                         print "Added slice attribute", slice_tag_id, "of type", tag_type_id,
1568                         if node_id is not None:
1569                             print "to node", node_id,
1570                         print
1571                         
1572     def UpdateSliceTags(self):
1573         """
1574         Make random changes to any slice attributes we may have added.
1575         """
1576
1577         for slice_tag_id in self.slice_tag_ids:
1578             # Update slice attribute
1579             value = randstr(16, letters + '_' + digits)
1580             self.api.UpdateSliceTag(slice_tag_id, value)
1581
1582             # Check slice attribute again
1583             slice_tag = self.api.GetSliceTags([slice_tag_id])[0]
1584             assert slice_tag['value'] == value
1585
1586             if self.verbose:
1587                 print "Updated slice attribute", slice_tag_id
1588
1589     def DeleteSliceTags(self):
1590         """
1591         Delete any random slice attributes we may have added.
1592         """
1593
1594         for slice_tag_id in self.slice_tag_ids:
1595             self.api.DeleteSliceTag(slice_tag_id)
1596
1597             if self.check:
1598                 assert not self.api.GetSliceTags([slice_tag_id])
1599
1600             if self.verbose:
1601                 print "Deleted slice attribute", slice_tag_id
1602
1603         if self.check:
1604             assert not self.api.GetSliceTags(self.slice_tag_ids)
1605
1606         self.slice_tag_ids = []
1607
1608 def main():
1609     parser = OptionParser()
1610     parser.add_option("-c", "--check", action = "store_true", default = False, 
1611                       help = "Check most actions (default: %default)")
1612     parser.add_option("-q", "--quiet", action = "store_true", default = False, 
1613                       help = "Be quiet (default: %default)")
1614     parser.add_option("-p","--preserve", action="store_true", default =False,
1615                       help = "Do not delete created objects")
1616     parser.add_option("-t", "--tiny", action = "store_true", default = False, 
1617                       help = "Run a tiny test (default: %default)")
1618     parser.add_option("-s", "--short-names", action="store_true", dest="short_names", default = False, 
1619                       help = "Generate smaller names for checking UI rendering")
1620     (options, args) = parser.parse_args()
1621
1622     test = Test(api = Shell(),
1623                 check = options.check,
1624                 verbose = not options.quiet,
1625                 preserve = options.preserve)
1626
1627     if options.tiny:
1628         sizes = Test.sizes_tiny
1629     else:
1630         sizes = Test.sizes_default
1631
1632     if options.short_names:
1633         test.namelengths = Test.namelengths_short
1634     else:
1635         test.namelengths = Test.namelengths_default
1636
1637     test.Run(**sizes)
1638
1639 if __name__ == "__main__":
1640     main()