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