- import Debug.log
[plcapi.git] / PLC / Test.py
1 #!/usr/bin/python
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: Test.py,v 1.19 2007/01/09 16:23:47 mlhuang Exp $
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_nodenetwork(method, type):
153     nodenetwork_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             nodenetwork_fields[field] = socket.inet_ntoa(struct.pack('>L', locals()[field]))
169
170     return nodenetwork_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         'nodenetworks_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         'nodegroups': 10,
236         'nodes_per_site': 2,
237         'nodenetworks_per_node': 1,
238         'pcus_per_site': 1,
239         'conf_files': 10,
240         'attribute_types': 10,
241         'slices_per_site': 10,
242         'attributes_per_slice': 2,
243         }
244
245     def __init__(self, api, check = True, verbose = True):
246         self.api = api
247         self.check = check
248         self.verbose = verbose
249         
250         self.site_ids = []
251         self.address_type_ids = []
252         self.address_ids = []
253         self.person_ids = []
254         self.key_ids = []
255         self.nodegroup_ids = []
256         self.node_ids = []
257         self.nodenetwork_ids = []
258         self.pcu_ids = []
259         self.conf_file_ids = []
260         self.attribute_type_ids = []
261         self.slice_ids = []
262         self.slice_attribute_ids = []
263
264     def Run(self, **kwds):
265         """
266         Run a complete database and API consistency test. Populates
267         the database with a set of random entities, updates them, then
268         deletes them. Examples:
269
270         test.Run() # Defaults
271         test.Run(**Test.default) # Defaults
272         test.Run(**Test.tiny) # Tiny set
273         test.Run(sites = 123, slices_per_site = 4) # Defaults with overrides
274         """
275
276         try:
277             self.Add(**kwds)
278             self.Update()
279         finally:
280             self.Delete()
281
282     def Add(self, **kwds):
283         """
284         Populate the database with a set of random entities. Examples:
285
286         test.populate() # Defaults
287         test.populate(Test.tiny) # Tiny set
288         test.populate(sites = 123, slices_per_site = 4) # Defaults with overrides
289         """
290
291         params = self.default.copy()
292         params.update(kwds)
293
294         self.AddSites(params['sites'])
295         self.AddAddressTypes(params['address_types'])
296         self.AddAddresses(params['addresses_per_site'])
297         self.AddPersons(params['persons_per_site'])
298         self.AddKeys(params['keys_per_person'])
299         self.AddNodeGroups(params['nodegroups'])
300         self.AddNodes(params['nodes_per_site'])
301         self.AddNodeNetworks(params['nodenetworks_per_node'])
302         self.AddPCUs(params['pcus_per_site'])
303         self.AddConfFiles(params['conf_files'])
304         self.AddSliceAttributeTypes(params['attribute_types'])
305         self.AddSlices(params['slices_per_site'])
306         self.AddSliceAttributes(params['attributes_per_slice'])
307
308     def Update(self):
309         self.UpdateSites()
310         self.UpdateAddressTypes()
311         self.UpdateAddresses()
312         self.UpdatePersons()
313         self.UpdateKeys()
314         self.UpdateNodeGroups()
315         self.UpdateNodes()
316         self.UpdateNodeNetworks()
317         self.UpdatePCUs()
318         self.UpdateConfFiles()
319         self.UpdateSliceAttributeTypes()
320         self.UpdateSlices()
321         self.UpdateSliceAttributes()
322
323     def Delete(self):
324         self.DeleteSliceAttributes()
325         self.DeleteSlices()
326         self.DeleteSliceAttributeTypes()
327         self.DeleteKeys()
328         self.DeleteConfFiles()
329         self.DeletePCUs()
330         self.DeleteNodeNetworks()
331         self.DeleteNodes()
332         self.DeletePersons()
333         self.DeleteNodeGroups()
334         self.DeleteAddresses()
335         self.DeleteAddressTypes()
336         self.DeleteSites()
337
338     def AddSites(self, n = 10):
339         """
340         Add a number of random sites.
341         """
342
343         for i in range(n):
344             # Add site
345             site_fields = random_site()
346             site_id = self.api.AddSite(site_fields)
347
348             # Should return a unique site_id
349             assert site_id not in self.site_ids
350             self.site_ids.append(site_id)
351
352             # Enable slice creation
353             site_fields['max_slices'] = randint(1, 10)
354             self.api.UpdateSite(site_id, site_fields)
355
356             if self.check:
357                 # Check site
358                 site = self.api.GetSites([site_id])[0]
359                 for field in site_fields:
360                     assert site[field] == site_fields[field]
361
362             if self.verbose:
363                 print "Added site", site_id
364
365     def UpdateSites(self):
366         """
367         Make random changes to any sites we may have added.
368         """
369
370         for site_id in self.site_ids:
371             # Update site
372             site_fields = random_site()
373             # Do not change login_base
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             self.api.UpdatePerson(person_id, person_fields)
609
610             if self.check:
611                 # Check user
612                 person = self.api.GetPersons([person_id])[0]
613                 for field in person_fields:
614                     if field != 'password':
615                         assert person[field] == person_fields[field]
616
617             if self.verbose:
618                 print "Updated person", person_id
619
620             person = self.api.GetPersons([person_id])[0]
621
622             # Associate user with a random set of sites
623             site_ids = random.sample(self.site_ids, randint(0, len(self.site_ids)))
624             for site_id in (set(site_ids) - set(person['site_ids'])):
625                 self.api.AddPersonToSite(person_id, site_id)
626             for site_id in (set(person['site_ids']) - set(site_ids)):
627                 self.api.DeletePersonFromSite(person_id, site_id)
628
629             if site_ids:
630                 self.api.SetPersonPrimarySite(person_id, site_ids[0])
631
632             if self.check:
633                 person = self.api.GetPersons([person_id])[0]
634                 assert set(site_ids) == set(person['site_ids'])
635
636             if self.verbose:
637                 print "Updated person", person_id, "to sites", site_ids
638
639     def DeletePersons(self):
640         """
641         Delete any random users we may have added.
642         """
643
644         for person_id in self.person_ids:
645             # Remove from site
646             person = self.api.GetPersons([person_id])[0]
647             for site_id in person['site_ids']:
648                 self.api.DeletePersonFromSite(person_id, site_id)
649
650             if self.check:
651                 person = self.api.GetPersons([person_id])[0]
652                 assert not person['site_ids']
653
654             # Revoke roles
655             for role_id in person['role_ids']:
656                 self.api.DeleteRoleFromPerson(role_id, person_id)
657
658             if self.check:
659                 person = self.api.GetPersons([person_id])[0]
660                 assert not person['role_ids']
661
662             # Disable account
663             self.api.UpdatePerson(person_id, {'enabled': False})
664
665             if self.check:
666                 person = self.api.GetPersons([person_id])[0]
667                 assert not person['enabled']
668
669             # Delete account
670             self.api.DeletePerson(person_id)
671
672             if self.check:
673                 assert not self.api.GetPersons([person_id])                         
674
675             if self.verbose:
676                 print "Deleted user", person_id
677
678         if self.check:
679             assert not self.api.GetPersons(self.person_ids)
680
681         self.person_ids = []
682
683     def AddKeys(self, per_person = 2):
684         """
685         Add a number of random keys to each user.
686         """
687
688         key_types = self.api.GetKeyTypes()
689         if not key_types:
690             raise Exception, "No key types"
691
692         for person_id in self.person_ids:
693             for i in range(per_person):
694                 # Add key
695                 key_fields = random_key(key_types)
696                 key_id = self.api.AddPersonKey(person_id, key_fields)
697
698                 # Should return a unique key_id
699                 assert key_id not in self.key_ids
700                 self.key_ids.append(key_id)
701
702                 if self.check:
703                     # Check key
704                     key = self.api.GetKeys([key_id])[0]
705                     for field in key_fields:
706                         assert key[field] == key_fields[field]
707
708                     # Add and immediately blacklist a key
709                     key_fields = random_key(key_types)
710                     key_id = self.api.AddPersonKey(person_id, key_fields)
711
712                     self.api.BlacklistKey(key_id)
713
714                     # Is effectively deleted
715                     assert not self.api.GetKeys([key_id])
716
717                     # Cannot be added again
718                     try:
719                         key_id = self.api.AddPersonKey(person_id, key_fields)
720                         assert False
721                     except Exception, e:
722                         pass
723
724                 if self.verbose:
725                     print "Added key", key_id, "to user", person_id
726
727     def UpdateKeys(self):
728         """
729         Make random changes to any keys we may have added.
730         """
731
732         key_types = self.api.GetKeyTypes()
733         if not key_types:
734             raise Exception, "No key types"
735
736         for key_id in self.key_ids:
737             # Update key
738             key_fields = random_key(key_types)
739             self.api.UpdateKey(key_id, key_fields)
740
741             if self.check:
742                 # Check key
743                 key = self.api.GetKeys([key_id])[0]
744                 for field in key_fields:
745                     assert key[field] == key_fields[field]
746
747             if self.verbose:
748                 print "Updated key", key_id
749
750     def DeleteKeys(self):
751         """
752         Delete any random keys we may have added.
753         """
754
755         for key_id in self.key_ids:
756             self.api.DeleteKey(key_id)
757
758             if self.check:
759                 assert not self.api.GetKeys([key_id])
760
761             if self.verbose:
762                 print "Deleted key", key_id
763
764         if self.check:
765             assert not self.api.GetKeys(self.key_ids)
766
767         self.key_ids = []
768
769     def AddNodeGroups(self, n = 10):
770         """
771         Add a number of random node groups.
772         """
773
774         for i in range(n):
775             # Add node group
776             nodegroup_fields = random_nodegroup()
777             nodegroup_id = self.api.AddNodeGroup(nodegroup_fields)
778
779             # Should return a unique nodegroup_id
780             assert nodegroup_id not in self.nodegroup_ids
781             self.nodegroup_ids.append(nodegroup_id)
782
783             if self.check:
784                 # Check node group
785                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
786                 for field in nodegroup_fields:
787                     assert nodegroup[field] == nodegroup_fields[field]
788
789             if self.verbose:
790                 print "Added node group", nodegroup_id
791
792     def UpdateNodeGroups(self):
793         """
794         Make random changes to any node groups we may have added.
795         """
796
797         for nodegroup_id in self.nodegroup_ids:
798             # Update nodegroup
799             nodegroup_fields = random_nodegroup()
800             self.api.UpdateNodeGroup(nodegroup_id, nodegroup_fields)
801
802             if self.check:
803                 # Check nodegroup
804                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
805                 for field in nodegroup_fields:
806                     assert nodegroup[field] == nodegroup_fields[field]
807
808             if self.verbose:
809                 print "Updated node group", nodegroup_id
810
811     def DeleteNodeGroups(self):
812         """
813         Delete any random node groups we may have added.
814         """
815
816         for nodegroup_id in self.nodegroup_ids:
817             self.api.DeleteNodeGroup(nodegroup_id)
818
819             if self.check:
820                 assert not self.api.GetNodeGroups([nodegroup_id])
821
822             if self.verbose:
823                 print "Deleted node group", nodegroup_id
824
825         if self.check:
826             assert not self.api.GetNodeGroups(self.nodegroup_ids)
827
828         self.nodegroup_ids = []
829
830     def AddNodes(self, per_site = 2):
831         """
832         Add a number of random nodes to each site. Each node will also
833         be added to a random node group if AddNodeGroups() was
834         previously run.
835         """
836         
837         boot_states = self.api.GetBootStates()
838         if not boot_states:
839             raise Exception, "No boot states"
840
841         for site_id in self.site_ids:
842             for i in range(per_site):
843                 # Add node
844                 node_fields = random_node(boot_states)
845                 node_id = self.api.AddNode(site_id, node_fields)
846
847                 # Should return a unique node_id
848                 assert node_id not in self.node_ids
849                 self.node_ids.append(node_id)
850
851                 # Add to a random set of node groups
852                 nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
853                 for nodegroup_id in nodegroup_ids:
854                     self.api.AddNodeToNodeGroup(node_id, nodegroup_id)
855
856                 if self.check:
857                     # Check node
858                     node = self.api.GetNodes([node_id])[0]
859                     for field in node_fields:
860                         assert node[field] == node_fields[field]
861
862                 if self.verbose:
863                     print "Added node", node_id
864
865     def UpdateNodes(self):
866         """
867         Make random changes to any nodes we may have added.
868         """
869
870         boot_states = self.api.GetBootStates()
871         if not boot_states:
872             raise Exception, "No boot states"
873
874         for node_id in self.node_ids:
875             # Update node
876             node_fields = random_node(boot_states)
877             self.api.UpdateNode(node_id, node_fields)
878
879             node = self.api.GetNodes([node_id])[0]
880
881             # Add to a random set of node groups
882             nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
883             for nodegroup_id in (set(nodegroup_ids) - set(node['nodegroup_ids'])):
884                 self.api.AddNodeToNodeGroup(node_id, nodegroup_id)
885             for nodegroup_id in (set(node['nodegroup_ids']) - set(nodegroup_ids)):
886                 self.api.DeleteNodeFromNodeGroup(node_id, nodegroup_id)
887
888             if self.check:
889                 # Check node
890                 node = self.api.GetNodes([node_id])[0]
891                 for field in node_fields:
892                     assert node[field] == node_fields[field]
893                 assert set(nodegroup_ids) == set(node['nodegroup_ids'])
894
895             if self.verbose:
896                 print "Updated node", node_id
897                 print "Added node", node_id, "to node groups", nodegroup_ids
898
899     def DeleteNodes(self):
900         """
901         Delete any random nodes we may have added.
902         """
903
904         for node_id in self.node_ids:
905             # Remove from node groups
906             node = self.api.GetNodes([node_id])[0]
907             for nodegroup_id in node['nodegroup_ids']:
908                 self.api.DeleteNodeFromNodeGroup(node_id, nodegroup_id)
909
910             if self.check:
911                 node = self.api.GetNodes([node_id])[0]
912                 assert not node['nodegroup_ids']
913
914             self.api.DeleteNode(node_id)
915
916             if self.check:
917                 assert not self.api.GetNodes([node_id])
918
919             if self.verbose:
920                 print "Deleted node", node_id
921
922         if self.check:
923             assert not self.api.GetNodes(self.node_ids)
924
925         self.node_ids = []
926
927     def AddNodeNetworks(self, per_node = 1):
928         """
929         Add a number of random network interfaces to each node.
930         """
931
932         network_methods = self.api.GetNetworkMethods()
933         if not network_methods:
934             raise Exception, "No network methods"
935         
936         network_types = self.api.GetNetworkTypes()
937         if not network_types:
938             raise Exception, "No network types"
939
940         for node_id in self.node_ids:
941             for i in range(per_node):
942                 method = random.sample(network_methods, 1)[0]
943                 type = random.sample(network_types, 1)[0]
944
945                 # Add node network
946                 nodenetwork_fields = random_nodenetwork(method, type)
947                 nodenetwork_id = self.api.AddNodeNetwork(node_id, nodenetwork_fields)
948
949                 # Should return a unique nodenetwork_id
950                 assert nodenetwork_id not in self.nodenetwork_ids
951                 self.nodenetwork_ids.append(nodenetwork_id)
952
953                 if self.check:
954                     # Check node network
955                     nodenetwork = self.api.GetNodeNetworks([nodenetwork_id])[0]
956                     for field in nodenetwork_fields:
957                         assert nodenetwork[field] == nodenetwork_fields[field]
958
959                 if self.verbose:
960                     print "Added node network", nodenetwork_id, "to node", node_id
961
962     def UpdateNodeNetworks(self):
963         """
964         Make random changes to any network interfaces we may have added.
965         """
966
967         network_methods = self.api.GetNetworkMethods()
968         if not network_methods:
969             raise Exception, "No network methods"
970         
971         network_types = self.api.GetNetworkTypes()
972         if not network_types:
973             raise Exception, "No network types"
974
975         for nodenetwork_id in self.nodenetwork_ids:
976             method = random.sample(network_methods, 1)[0]
977             type = random.sample(network_types, 1)[0]
978
979             # Update nodenetwork
980             nodenetwork_fields = random_nodenetwork(method, type)
981             self.api.UpdateNodeNetwork(nodenetwork_id, nodenetwork_fields)
982
983             if self.check:
984                 # Check nodenetwork
985                 nodenetwork = self.api.GetNodeNetworks([nodenetwork_id])[0]
986                 for field in nodenetwork_fields:
987                     assert nodenetwork[field] == nodenetwork_fields[field]
988
989             if self.verbose:
990                 print "Updated node network", nodenetwork_id
991
992     def DeleteNodeNetworks(self):
993         """
994         Delete any random network interfaces we may have added.
995         """
996
997         for nodenetwork_id in self.nodenetwork_ids:
998             self.api.DeleteNodeNetwork(nodenetwork_id)
999
1000             if self.check:
1001                 assert not self.api.GetNodeNetworks([nodenetwork_id])
1002
1003             if self.verbose:
1004                 print "Deleted node network", nodenetwork_id
1005
1006         if self.check:
1007             assert not self.api.GetNodeNetworks(self.nodenetwork_ids)
1008
1009         self.nodenetwork_ids = []
1010
1011     def AddPCUs(self, per_site = 1):
1012         """
1013         Add a number of random PCUs to each site. Each node at the
1014         site will be added to a port on the PCU if AddNodes() was
1015         previously run.
1016         """
1017
1018         for site_id in self.site_ids:
1019             for i in range(per_site):
1020                 # Add PCU
1021                 pcu_fields = random_pcu()
1022                 pcu_id = self.api.AddPCU(site_id, pcu_fields)
1023
1024                 # Should return a unique pcu_id
1025                 assert pcu_id not in self.pcu_ids
1026                 self.pcu_ids.append(pcu_id)
1027
1028                 # Add each node at this site to a different port on this PCU
1029                 site = self.api.GetSites([site_id])[0]
1030                 port = randint(1, 10)
1031                 for node_id in site['node_ids']:
1032                     self.api.AddNodeToPCU(node_id, pcu_id, port)
1033                     port += 1
1034
1035                 if self.check:
1036                     # Check PCU
1037                     pcu = self.api.GetPCUs([pcu_id])[0]
1038                     for field in pcu_fields:
1039                         assert pcu[field] == pcu_fields[field]
1040
1041                 if self.verbose:
1042                     print "Added PCU", pcu_id, "to site", site_id
1043
1044     def UpdatePCUs(self):
1045         """
1046         Make random changes to any PCUs we may have added.
1047         """
1048
1049         for pcu_id in self.pcu_ids:
1050             # Update PCU
1051             pcu_fields = random_pcu()
1052             self.api.UpdatePCU(pcu_id, pcu_fields)
1053
1054             if self.check:
1055                 # Check PCU
1056                 pcu = self.api.GetPCUs([pcu_id])[0]
1057                 for field in pcu_fields:
1058                     assert pcu[field] == pcu_fields[field]
1059
1060             if self.verbose:
1061                 print "Updated PCU", pcu_id
1062
1063     def DeletePCUs(self):
1064         """
1065         Delete any random nodes we may have added.
1066         """
1067
1068         for pcu_id in self.pcu_ids:
1069             # Remove nodes from PCU
1070             pcu = self.api.GetPCUs([pcu_id])[0]
1071             for node_id in pcu['node_ids']:
1072                 self.api.DeleteNodeFromPCU(node_id, pcu_id)
1073
1074             if self.check:
1075                 pcu = self.api.GetPCUs([pcu_id])[0]
1076                 assert not pcu['node_ids']
1077
1078             self.api.DeletePCU(pcu_id)
1079
1080             if self.check:
1081                 assert not self.api.GetPCUs([pcu_id])
1082
1083             if self.verbose:
1084                 print "Deleted PCU", pcu_id
1085
1086         if self.check:
1087             assert not self.api.GetPCUs(self.pcu_ids)
1088
1089         self.pcu_ids = []
1090
1091     def AddConfFiles(self, n = 10):
1092         """
1093         Add a number of random global configuration files.
1094         """
1095
1096         conf_files = []
1097
1098         for i in range(n):
1099             # Add a random configuration file
1100             conf_files.append(random_conf_file())
1101
1102         if n:
1103             # Add a nodegroup override file
1104             nodegroup_conf_file = conf_files[0].copy()
1105             nodegroup_conf_file['source'] = randpath(255)
1106             conf_files.append(nodegroup_conf_file)
1107
1108             # Add a node override file
1109             node_conf_file = conf_files[0].copy()
1110             node_conf_file['source'] = randpath(255)
1111             conf_files.append(node_conf_file)
1112
1113         for conf_file_fields in conf_files:
1114             conf_file_id = self.api.AddConfFile(conf_file_fields)
1115
1116             # Should return a unique conf_file_id
1117             assert conf_file_id not in self.conf_file_ids
1118             self.conf_file_ids.append(conf_file_id)
1119
1120             # Add to nodegroup
1121             if conf_file_fields == nodegroup_conf_file and self.nodegroup_ids:
1122                 nodegroup_id = random.sample(self.nodegroup_ids, 1)[0]
1123                 self.api.AddConfFileToNodeGroup(conf_file_id, nodegroup_id)
1124             else:
1125                 nodegroup_id = None
1126
1127             # Add to node
1128             if conf_file_fields == node_conf_file and self.node_ids:
1129                 node_id = random.sample(self.node_ids, 1)[0]
1130                 self.api.AddConfFileToNode(conf_file_id, node_id)
1131             else:
1132                 node_id = None
1133
1134             if self.check:
1135                 # Check configuration file
1136                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1137                 for field in conf_file_fields:
1138                     assert conf_file[field] == conf_file_fields[field]
1139
1140             if self.verbose:
1141                 print "Added configuration file", conf_file_id,
1142                 if nodegroup_id is not None:
1143                     print "to node group", nodegroup_id,
1144                 elif node_id is not None:
1145                     print "to node", node_id,
1146                 print
1147
1148     def UpdateConfFiles(self):
1149         """
1150         Make random changes to any configuration files we may have added.
1151         """
1152
1153         for conf_file_id in self.conf_file_ids:
1154             # Update configuration file
1155             conf_file_fields = random_conf_file()
1156             # Do not update dest so that it remains an override if set
1157             del conf_file_fields['dest']
1158             self.api.UpdateConfFile(conf_file_id, conf_file_fields)
1159
1160             if self.check:
1161                 # Check configuration file
1162                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1163                 for field in conf_file_fields:
1164                     assert conf_file[field] == conf_file_fields[field]
1165
1166             if self.verbose:
1167                 print "Updated configuration file", conf_file_id
1168
1169     def DeleteConfFiles(self):
1170         """
1171         Delete any random configuration files we may have added.
1172         """
1173
1174         for conf_file_id in self.conf_file_ids:
1175             self.api.DeleteConfFile(conf_file_id)
1176
1177             if self.check:
1178                 assert not self.api.GetConfFiles([conf_file_id])
1179
1180             if self.verbose:
1181                 print "Deleted configuration file", conf_file_id
1182
1183         if self.check:
1184             assert not self.api.GetConfFiles(self.conf_file_ids)
1185
1186         self.conf_file_ids = []
1187
1188     def AddSliceAttributeTypes(self, n = 10):
1189         """
1190         Add a number of random slice attribute types.
1191         """
1192
1193         roles = self.api.GetRoles()
1194         if not roles:
1195             raise Exception, "No roles"
1196         role_ids = [role['role_id'] for role in roles]
1197
1198         for i in range(n):
1199             attribute_type_fields = random_attribute_type(role_ids)
1200             attribute_type_id = self.api.AddSliceAttributeType(attribute_type_fields)
1201
1202             # Should return a unique attribute_type_id
1203             assert attribute_type_id not in self.attribute_type_ids
1204             self.attribute_type_ids.append(attribute_type_id)
1205
1206             if self.check:
1207                 # Check slice attribute type
1208                 attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
1209                 for field in attribute_type_fields:
1210                     assert attribute_type[field] == attribute_type_fields[field]
1211
1212             if self.verbose:
1213                 print "Added slice attribute type", attribute_type_id
1214
1215     def UpdateSliceAttributeTypes(self):
1216         """
1217         Make random changes to any slice attribute types we may have added.
1218         """
1219
1220         roles = self.api.GetRoles()
1221         if not roles:
1222             raise Exception, "No roles"
1223         role_ids = [role['role_id'] for role in roles]
1224
1225         for attribute_type_id in self.attribute_type_ids:
1226             # Update slice attribute type
1227             attribute_type_fields = random_attribute_type(role_ids)
1228             self.api.UpdateSliceAttributeType(attribute_type_id, attribute_type_fields)
1229
1230             if self.check:
1231                 # Check slice attribute type
1232                 attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
1233                 for field in attribute_type_fields:
1234                     assert attribute_type[field] == attribute_type_fields[field]
1235
1236             if self.verbose:
1237                 print "Updated slice attribute type", attribute_type_id
1238
1239     def DeleteSliceAttributeTypes(self):
1240         """
1241         Delete any random slice attribute types we may have added.
1242         """
1243
1244         for attribute_type_id in self.attribute_type_ids:
1245             self.api.DeleteSliceAttributeType(attribute_type_id)
1246
1247             if self.check:
1248                 assert not self.api.GetSliceAttributeTypes([attribute_type_id])
1249
1250             if self.verbose:
1251                 print "Deleted slice attribute type", attribute_type_id
1252
1253         if self.check:
1254             assert not self.api.GetSliceAttributeTypes(self.attribute_type_ids)
1255
1256         self.attribute_type_ids = []
1257
1258     def AddSlices(self, per_site = 10):
1259         """
1260         Add a number of random slices per site.
1261         """
1262
1263         for site in self.api.GetSites(self.site_ids):
1264             for i in range(min(per_site, site['max_slices'])):
1265                 # Add slice
1266                 slice_fields = random_slice(site['login_base'])
1267                 slice_id = self.api.AddSlice(slice_fields)
1268
1269                 # Should return a unique slice_id
1270                 assert slice_id not in self.slice_ids
1271                 self.slice_ids.append(slice_id)
1272
1273                 # Add slice to a random set of nodes
1274                 node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1275                 if node_ids:
1276                     self.api.AddSliceToNodes(slice_id, node_ids)
1277
1278                 # Add random set of site users to slice
1279                 person_ids = random.sample(site['person_ids'], randint(0, len(site['person_ids'])))
1280                 for person_id in person_ids:
1281                     self.api.AddPersonToSlice(person_id, slice_id)
1282
1283                 if self.check:
1284                     # Check slice
1285                     slice = self.api.GetSlices([slice_id])[0]
1286                     for field in slice_fields:
1287                         assert slice[field] == slice_fields[field]
1288
1289                     assert set(node_ids) == set(slice['node_ids'])
1290                     assert set(person_ids) == set(slice['person_ids'])
1291
1292                 if self.verbose:
1293                     print "Added slice", slice_id, "to site", site['site_id'],
1294                     if node_ids:
1295                         print "and nodes", node_ids,
1296                     print
1297                     if person_ids:
1298                         print "Added users", site['person_ids'], "to slice", slice_id
1299
1300     def UpdateSlices(self):
1301         """
1302         Make random changes to any slices we may have added.
1303         """
1304
1305         for slice_id in self.slice_ids:
1306             # Update slice
1307             slice_fields = random_slice("unused")
1308             # Cannot change slice name
1309             del slice_fields['name']
1310             self.api.UpdateSlice(slice_id, slice_fields)
1311
1312             slice = self.api.GetSlices([slice_id])[0]
1313
1314             # Add slice to a random set of nodes
1315             node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1316             self.api.AddSliceToNodes(slice_id, list(set(node_ids) - set(slice['node_ids'])))
1317             self.api.DeleteSliceFromNodes(slice_id, list(set(slice['node_ids']) - set(node_ids)))
1318
1319             # Add random set of users to slice
1320             person_ids = random.sample(self.person_ids, randint(0, len(self.person_ids)))
1321             for person_id in (set(person_ids) - set(slice['person_ids'])):
1322                 self.api.AddPersonToSlice(person_id, slice_id)
1323             for person_id in (set(slice['person_ids']) - set(person_ids)):
1324                 self.api.DeletePersonFromSlice(person_id, slice_id)
1325
1326             if self.check:
1327                 slice = self.api.GetSlices([slice_id])[0]
1328                 for field in slice_fields:
1329                     assert slice[field] == slice_fields[field]
1330                 assert set(node_ids) == set(slice['node_ids'])
1331                 assert set(person_ids) == set(slice['person_ids'])
1332
1333             if self.verbose:
1334                 print "Updated slice", slice_id
1335                 print "Added nodes", node_ids, "to slice", slice_id
1336                 print "Added persons", person_ids, "to slice", slice_id
1337
1338     def DeleteSlices(self):
1339         """
1340         Delete any random slices we may have added.
1341         """
1342
1343         for slice_id in self.slice_ids:
1344             self.api.DeleteSlice(slice_id)
1345
1346             if self.check:
1347                 assert not self.api.GetSlices([slice_id])
1348
1349             if self.verbose:
1350                 print "Deleted slice", slice_id
1351
1352         if self.check:
1353             assert not self.api.GetSlices(self.slice_ids)
1354
1355         self.slice_ids = []
1356
1357     def AddSliceAttributes(self, per_slice = 2):
1358         """
1359         Add a number of random slices per site.
1360         """
1361
1362         if not self.attribute_type_ids:
1363             return
1364
1365         for slice_id in self.slice_ids:
1366             slice = self.api.GetSlices([slice_id])[0]
1367
1368             for i in range(per_slice):
1369                 # Set a random slice/sliver attribute
1370                 for attribute_type_id in random.sample(self.attribute_type_ids, 1):
1371                     value = randstr(16, letters + '_' + digits)
1372                     # Make it a sliver attribute with 50% probability
1373                     if slice['node_ids']:
1374                         node_id = random.sample(slice['node_ids'] + [None] * len(slice['node_ids']), 1)[0]
1375                     else:
1376                         node_id = None
1377
1378                     # Add slice attribute
1379                     if node_id is None:
1380                         slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value)
1381                     else:
1382                         slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value, node_id)
1383
1384                     # Should return a unique slice_attribute_id
1385                     assert slice_attribute_id not in self.slice_attribute_ids
1386                     self.slice_attribute_ids.append(slice_attribute_id)
1387
1388                     if self.check:
1389                         # Check slice attribute
1390                         slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
1391                         for field in 'attribute_type_id', 'slice_id', 'node_id', 'slice_attribute_id', 'value':
1392                             assert slice_attribute[field] == locals()[field]
1393
1394                     if self.verbose:
1395                         print "Added slice attribute", slice_attribute_id, "of type", attribute_type_id,
1396                         if node_id is not None:
1397                             print "to node", node_id,
1398                         print
1399                         
1400     def UpdateSliceAttributes(self):
1401         """
1402         Make random changes to any slice attributes we may have added.
1403         """
1404
1405         for slice_attribute_id in self.slice_attribute_ids:
1406             # Update slice attribute
1407             value = randstr(16, letters + '_' + digits)
1408             self.api.UpdateSliceAttribute(slice_attribute_id, value)
1409
1410             # Check slice attribute again
1411             slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
1412             assert slice_attribute['value'] == value
1413
1414             if self.verbose:
1415                 print "Updated slice attribute", slice_attribute_id
1416
1417     def DeleteSliceAttributes(self):
1418         """
1419         Delete any random slice attributes we may have added.
1420         """
1421
1422         for slice_attribute_id in self.slice_attribute_ids:
1423             self.api.DeleteSliceAttribute(slice_attribute_id)
1424
1425             if self.check:
1426                 assert not self.api.GetSliceAttributes([slice_attribute_id])
1427
1428             if self.verbose:
1429                 print "Deleted slice attribute", slice_attribute_id
1430
1431         if self.check:
1432             assert not self.api.GetSliceAttributes(self.slice_attribute_ids)
1433
1434         self.slice_attribute_ids = []
1435
1436 def main():
1437     parser = OptionParser()
1438     parser.add_option("-c", "--check", action = "store_true", default = False, help = "Check most actions (default: %default)")
1439     parser.add_option("-q", "--quiet", action = "store_true", default = False, help = "Be quiet (default: %default)")
1440     parser.add_option("-t", "--tiny", action = "store_true", default = False, help = "Run a tiny test (default: %default)")
1441     (options, args) = parser.parse_args()
1442
1443     test = Test(api = Shell(),
1444                 check = options.check,
1445                 verbose = not options.quiet)
1446
1447     if options.tiny:
1448         params = Test.tiny
1449     else:
1450         params = Test.default
1451
1452     test.Run(**params)
1453
1454 if __name__ == "__main__":
1455     main()