Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[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 5574 2007-10-25 20:33:17Z 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_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             if 'login_base' in site_fields:
375                 del site_fields['login_base']
376             self.api.UpdateSite(site_id, site_fields)
377
378             if self.check:
379                 # Check site
380                 site = self.api.GetSites([site_id])[0]
381                 for field in site_fields:
382                     assert site[field] == site_fields[field]
383
384             if self.verbose:
385                 print "Updated site", site_id
386
387     def DeleteSites(self):
388         """
389         Delete any random sites we may have added.
390         """
391
392         for site_id in self.site_ids:
393             self.api.DeleteSite(site_id)
394
395             if self.check:
396                 assert not self.api.GetSites([site_id])
397
398             if self.verbose:
399                 print "Deleted site", site_id
400
401         if self.check:
402             assert not self.api.GetSites(self.site_ids)
403
404         self.site_ids = []
405
406     def AddAddressTypes(self, n = 2):
407         """
408         Add a number of random address types.
409         """
410
411         for i in range(n):
412             address_type_fields = random_address_type()
413             address_type_id = self.api.AddAddressType(address_type_fields)
414
415             # Should return a unique address_type_id
416             assert address_type_id not in self.address_type_ids
417             self.address_type_ids.append(address_type_id)
418
419             if self.check:
420                 # Check address type
421                 address_type = self.api.GetAddressTypes([address_type_id])[0]
422                 for field in address_type_fields:
423                     assert address_type[field] == address_type_fields[field]
424
425             if self.verbose:
426                 print "Added address type", address_type_id
427
428     def UpdateAddressTypes(self):
429         """
430         Make random changes to any address types we may have added.
431         """
432
433         for address_type_id in self.address_type_ids:
434             # Update address_type
435             address_type_fields = random_address_type()
436             self.api.UpdateAddressType(address_type_id, address_type_fields)
437
438             if self.check:
439                 # Check address type
440                 address_type = self.api.GetAddressTypes([address_type_id])[0]
441                 for field in address_type_fields:
442                     assert address_type[field] == address_type_fields[field]
443
444             if self.verbose:
445                 print "Updated address_type", address_type_id
446
447     def DeleteAddressTypes(self):
448         """
449         Delete any random address types we may have added.
450         """
451
452         for address_type_id in self.address_type_ids:
453             self.api.DeleteAddressType(address_type_id)
454
455             if self.check:
456                 assert not self.api.GetAddressTypes([address_type_id])
457
458             if self.verbose:
459                 print "Deleted address type", address_type_id
460
461         if self.check:
462             assert not self.api.GetAddressTypes(self.address_type_ids)
463
464         self.address_type_ids = []
465
466     def AddAddresses(self, per_site = 2):
467         """
468         Add a number of random addresses to each site.
469         """
470
471         for site_id in self.site_ids:
472             for i in range(per_site):
473                 address_fields = random_address()
474                 address_id = self.api.AddSiteAddress(site_id, address_fields)
475
476                 # Should return a unique address_id
477                 assert address_id not in self.address_ids
478                 self.address_ids.append(address_id)
479
480                 # Add random address type
481                 if self.address_type_ids:
482                     for address_type_id in random.sample(self.address_type_ids, 1):
483                         self.api.AddAddressTypeToAddress(address_type_id, address_id)
484
485                 if self.check:
486                     # Check address
487                     address = self.api.GetAddresses([address_id])[0]
488                     for field in address_fields:
489                         assert address[field] == address_fields[field]
490
491                 if self.verbose:
492                     print "Added address", address_id, "to site", site_id
493
494     def UpdateAddresses(self):
495         """
496         Make random changes to any addresses we may have added.
497         """
498
499         for address_id in self.address_ids:
500             # Update address
501             address_fields = random_address()
502             self.api.UpdateAddress(address_id, address_fields)
503
504             if self.check:
505                 # Check address
506                 address = self.api.GetAddresses([address_id])[0]
507                 for field in address_fields:
508                     assert address[field] == address_fields[field]
509
510             if self.verbose:
511                 print "Updated address", address_id
512
513     def DeleteAddresses(self):
514         """
515         Delete any random addresses we may have added.
516         """
517
518         for address_id in self.address_ids:
519             # Remove address types
520             address = self.api.GetAddresses([address_id])[0]
521             for address_type_id in address['address_type_ids']:
522                 self.api.DeleteAddressTypeFromAddress(address_type_id, address_id)
523
524             if self.check:
525                 address = self.api.GetAddresses([address_id])[0]
526                 assert not address['address_type_ids']
527
528             self.api.DeleteAddress(address_id)
529
530             if self.check:
531                 assert not self.api.GetAddresses([address_id])
532
533             if self.verbose:
534                 print "Deleted address", address_id
535
536         if self.check:
537             assert not self.api.GetAddresses(self.address_ids)
538
539         self.address_ids = []
540
541     def AddPersons(self, per_site = 10):
542         """
543         Add a number of random users to each site.
544         """
545
546         for site_id in self.site_ids:
547             for i in range(per_site):
548                 # Add user
549                 person_fields = random_person()
550                 person_id = self.api.AddPerson(person_fields)
551
552                 # Should return a unique person_id
553                 assert person_id not in self.person_ids
554                 self.person_ids.append(person_id)
555
556                 if self.check:
557                     # Check user
558                     person = self.api.GetPersons([person_id])[0]
559                     for field in person_fields:
560                         if field != 'password':
561                             assert person[field] == person_fields[field]
562
563                 auth = {'AuthMethod': "password",
564                         'Username': person_fields['email'],
565                         'AuthString': person_fields['password']}
566
567                 if self.check:
568                     # Check that user is disabled
569                     try:
570                         assert not self.api.AuthCheck(auth)
571                     except:
572                         pass
573
574                 # Add random set of roles
575                 role_ids = random.sample([20, 30, 40], randint(1, 3))
576                 for role_id in role_ids:
577                     self.api.AddRoleToPerson(role_id, person_id)
578
579                 if self.check:
580                     person = self.api.GetPersons([person_id])[0]
581                     assert set(role_ids) == set(person['role_ids'])
582
583                 # Enable user
584                 self.api.UpdatePerson(person_id, {'enabled': True})
585
586                 if self.check:
587                     # Check that user is enabled
588                     assert self.api.AuthCheck(auth)
589
590                 # Associate user with site
591                 self.api.AddPersonToSite(person_id, site_id)
592                 self.api.SetPersonPrimarySite(person_id, site_id)
593
594                 if self.check:
595                     person = self.api.GetPersons([person_id])[0]
596                     assert person['site_ids'][0] == site_id
597
598                 if self.verbose:
599                     print "Added user", person_id, "to site", site_id
600
601     def UpdatePersons(self):
602         """
603         Make random changes to any users we may have added.
604         """
605
606         for person_id in self.person_ids:
607             # Update user
608             person_fields = random_person()
609             # Keep them enabled
610             person_fields['enabled'] = True
611             self.api.UpdatePerson(person_id, person_fields)
612
613             if self.check:
614                 # Check user
615                 person = self.api.GetPersons([person_id])[0]
616                 for field in person_fields:
617                     if field != 'password':
618                         assert person[field] == person_fields[field]
619
620             if self.verbose:
621                 print "Updated person", person_id
622
623             person = self.api.GetPersons([person_id])[0]
624
625             # Associate user with a random set of sites
626             site_ids = random.sample(self.site_ids, randint(0, len(self.site_ids)))
627             for site_id in (set(site_ids) - set(person['site_ids'])):
628                 self.api.AddPersonToSite(person_id, site_id)
629             for site_id in (set(person['site_ids']) - set(site_ids)):
630                 self.api.DeletePersonFromSite(person_id, site_id)
631
632             if site_ids:
633                 self.api.SetPersonPrimarySite(person_id, site_ids[0])
634
635             if self.check:
636                 person = self.api.GetPersons([person_id])[0]
637                 assert set(site_ids) == set(person['site_ids'])
638
639             if self.verbose:
640                 print "Updated person", person_id, "to sites", site_ids
641
642     def DeletePersons(self):
643         """
644         Delete any random users we may have added.
645         """
646
647         for person_id in self.person_ids:
648             # Remove from site
649             person = self.api.GetPersons([person_id])[0]
650             for site_id in person['site_ids']:
651                 self.api.DeletePersonFromSite(person_id, site_id)
652
653             if self.check:
654                 person = self.api.GetPersons([person_id])[0]
655                 assert not person['site_ids']
656
657             # Revoke roles
658             for role_id in person['role_ids']:
659                 self.api.DeleteRoleFromPerson(role_id, person_id)
660
661             if self.check:
662                 person = self.api.GetPersons([person_id])[0]
663                 assert not person['role_ids']
664
665             # Disable account
666             self.api.UpdatePerson(person_id, {'enabled': False})
667
668             if self.check:
669                 person = self.api.GetPersons([person_id])[0]
670                 assert not person['enabled']
671
672             # Delete account
673             self.api.DeletePerson(person_id)
674
675             if self.check:
676                 assert not self.api.GetPersons([person_id])                         
677
678             if self.verbose:
679                 print "Deleted user", person_id
680
681         if self.check:
682             assert not self.api.GetPersons(self.person_ids)
683
684         self.person_ids = []
685
686     def AddKeys(self, per_person = 2):
687         """
688         Add a number of random keys to each user.
689         """
690
691         key_types = self.api.GetKeyTypes()
692         if not key_types:
693             raise Exception, "No key types"
694
695         for person_id in self.person_ids:
696             for i in range(per_person):
697                 # Add key
698                 key_fields = random_key(key_types)
699                 key_id = self.api.AddPersonKey(person_id, key_fields)
700
701                 # Should return a unique key_id
702                 assert key_id not in self.key_ids
703                 self.key_ids.append(key_id)
704
705                 if self.check:
706                     # Check key
707                     key = self.api.GetKeys([key_id])[0]
708                     for field in key_fields:
709                         assert key[field] == key_fields[field]
710
711                     # Add and immediately blacklist a key
712                     key_fields = random_key(key_types)
713                     key_id = self.api.AddPersonKey(person_id, key_fields)
714
715                     self.api.BlacklistKey(key_id)
716
717                     # Is effectively deleted
718                     assert not self.api.GetKeys([key_id])
719
720                     # Cannot be added again
721                     try:
722                         key_id = self.api.AddPersonKey(person_id, key_fields)
723                         assert False
724                     except Exception, e:
725                         pass
726
727                 if self.verbose:
728                     print "Added key", key_id, "to user", person_id
729
730     def UpdateKeys(self):
731         """
732         Make random changes to any keys we may have added.
733         """
734
735         key_types = self.api.GetKeyTypes()
736         if not key_types:
737             raise Exception, "No key types"
738
739         for key_id in self.key_ids:
740             # Update key
741             key_fields = random_key(key_types)
742             self.api.UpdateKey(key_id, key_fields)
743
744             if self.check:
745                 # Check key
746                 key = self.api.GetKeys([key_id])[0]
747                 for field in key_fields:
748                     assert key[field] == key_fields[field]
749
750             if self.verbose:
751                 print "Updated key", key_id
752
753     def DeleteKeys(self):
754         """
755         Delete any random keys we may have added.
756         """
757
758         for key_id in self.key_ids:
759             self.api.DeleteKey(key_id)
760
761             if self.check:
762                 assert not self.api.GetKeys([key_id])
763
764             if self.verbose:
765                 print "Deleted key", key_id
766
767         if self.check:
768             assert not self.api.GetKeys(self.key_ids)
769
770         self.key_ids = []
771
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             # Update nodegroup
802             nodegroup_fields = random_nodegroup()
803             self.api.UpdateNodeGroup(nodegroup_id, nodegroup_fields)
804
805             if self.check:
806                 # Check nodegroup
807                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
808                 for field in nodegroup_fields:
809                     assert nodegroup[field] == nodegroup_fields[field]
810
811             if self.verbose:
812                 print "Updated node group", nodegroup_id
813
814     def DeleteNodeGroups(self):
815         """
816         Delete any random node groups we may have added.
817         """
818
819         for nodegroup_id in self.nodegroup_ids:
820             self.api.DeleteNodeGroup(nodegroup_id)
821
822             if self.check:
823                 assert not self.api.GetNodeGroups([nodegroup_id])
824
825             if self.verbose:
826                 print "Deleted node group", nodegroup_id
827
828         if self.check:
829             assert not self.api.GetNodeGroups(self.nodegroup_ids)
830
831         self.nodegroup_ids = []
832
833     def AddNodes(self, per_site = 2):
834         """
835         Add a number of random nodes to each site. Each node will also
836         be added to a random node group if AddNodeGroups() was
837         previously run.
838         """
839         
840         boot_states = self.api.GetBootStates()
841         if not boot_states:
842             raise Exception, "No boot states"
843
844         for site_id in self.site_ids:
845             for i in range(per_site):
846                 # Add node
847                 node_fields = random_node(boot_states)
848                 node_id = self.api.AddNode(site_id, node_fields)
849
850                 # Should return a unique node_id
851                 assert node_id not in self.node_ids
852                 self.node_ids.append(node_id)
853
854                 # Add to a random set of node groups
855                 nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
856                 for nodegroup_id in nodegroup_ids:
857                     self.api.AddNodeToNodeGroup(node_id, nodegroup_id)
858
859                 if self.check:
860                     # Check node
861                     node = self.api.GetNodes([node_id])[0]
862                     for field in node_fields:
863                         assert node[field] == node_fields[field]
864
865                 if self.verbose:
866                     print "Added node", node_id
867
868     def UpdateNodes(self):
869         """
870         Make random changes to any nodes we may have added.
871         """
872
873         boot_states = self.api.GetBootStates()
874         if not boot_states:
875             raise Exception, "No boot states"
876
877         for node_id in self.node_ids:
878             # Update node
879             node_fields = random_node(boot_states)
880             self.api.UpdateNode(node_id, node_fields)
881
882             node = self.api.GetNodes([node_id])[0]
883
884             # Add to a random set of node groups
885             nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
886             for nodegroup_id in (set(nodegroup_ids) - set(node['nodegroup_ids'])):
887                 self.api.AddNodeToNodeGroup(node_id, nodegroup_id)
888             for nodegroup_id in (set(node['nodegroup_ids']) - set(nodegroup_ids)):
889                 self.api.DeleteNodeFromNodeGroup(node_id, nodegroup_id)
890
891             if self.check:
892                 # Check node
893                 node = self.api.GetNodes([node_id])[0]
894                 for field in node_fields:
895                     assert node[field] == node_fields[field]
896                 assert set(nodegroup_ids) == set(node['nodegroup_ids'])
897
898             if self.verbose:
899                 print "Updated node", node_id
900                 print "Added node", node_id, "to node groups", nodegroup_ids
901
902     def DeleteNodes(self):
903         """
904         Delete any random nodes we may have added.
905         """
906
907         for node_id in self.node_ids:
908             # Remove from node groups
909             node = self.api.GetNodes([node_id])[0]
910             for nodegroup_id in node['nodegroup_ids']:
911                 self.api.DeleteNodeFromNodeGroup(node_id, nodegroup_id)
912
913             if self.check:
914                 node = self.api.GetNodes([node_id])[0]
915                 assert not node['nodegroup_ids']
916
917             self.api.DeleteNode(node_id)
918
919             if self.check:
920                 assert not self.api.GetNodes([node_id])
921
922             if self.verbose:
923                 print "Deleted node", node_id
924
925         if self.check:
926             assert not self.api.GetNodes(self.node_ids)
927
928         self.node_ids = []
929
930     def AddNodeNetworks(self, per_node = 1):
931         """
932         Add a number of random network interfaces to each node.
933         """
934
935         network_methods = self.api.GetNetworkMethods()
936         if not network_methods:
937             raise Exception, "No network methods"
938         
939         network_types = self.api.GetNetworkTypes()
940         if not network_types:
941             raise Exception, "No network types"
942
943         for node_id in self.node_ids:
944             for i in range(per_node):
945                 method = random.sample(network_methods, 1)[0]
946                 type = random.sample(network_types, 1)[0]
947
948                 # Add node network
949                 nodenetwork_fields = random_nodenetwork(method, type)
950                 nodenetwork_id = self.api.AddNodeNetwork(node_id, nodenetwork_fields)
951
952                 # Should return a unique nodenetwork_id
953                 assert nodenetwork_id not in self.nodenetwork_ids
954                 self.nodenetwork_ids.append(nodenetwork_id)
955
956                 if self.check:
957                     # Check node network
958                     nodenetwork = self.api.GetNodeNetworks([nodenetwork_id])[0]
959                     for field in nodenetwork_fields:
960                         assert nodenetwork[field] == nodenetwork_fields[field]
961
962                 if self.verbose:
963                     print "Added node network", nodenetwork_id, "to node", node_id
964
965     def UpdateNodeNetworks(self):
966         """
967         Make random changes to any network interfaces we may have added.
968         """
969
970         network_methods = self.api.GetNetworkMethods()
971         if not network_methods:
972             raise Exception, "No network methods"
973         
974         network_types = self.api.GetNetworkTypes()
975         if not network_types:
976             raise Exception, "No network types"
977
978         for nodenetwork_id in self.nodenetwork_ids:
979             method = random.sample(network_methods, 1)[0]
980             type = random.sample(network_types, 1)[0]
981
982             # Update nodenetwork
983             nodenetwork_fields = random_nodenetwork(method, type)
984             self.api.UpdateNodeNetwork(nodenetwork_id, nodenetwork_fields)
985
986             if self.check:
987                 # Check nodenetwork
988                 nodenetwork = self.api.GetNodeNetworks([nodenetwork_id])[0]
989                 for field in nodenetwork_fields:
990                     assert nodenetwork[field] == nodenetwork_fields[field]
991
992             if self.verbose:
993                 print "Updated node network", nodenetwork_id
994
995     def DeleteNodeNetworks(self):
996         """
997         Delete any random network interfaces we may have added.
998         """
999
1000         for nodenetwork_id in self.nodenetwork_ids:
1001             self.api.DeleteNodeNetwork(nodenetwork_id)
1002
1003             if self.check:
1004                 assert not self.api.GetNodeNetworks([nodenetwork_id])
1005
1006             if self.verbose:
1007                 print "Deleted node network", nodenetwork_id
1008
1009         if self.check:
1010             assert not self.api.GetNodeNetworks(self.nodenetwork_ids)
1011
1012         self.nodenetwork_ids = []
1013
1014     def AddPCUs(self, per_site = 1):
1015         """
1016         Add a number of random PCUs to each site. Each node at the
1017         site will be added to a port on the PCU if AddNodes() was
1018         previously run.
1019         """
1020
1021         for site_id in self.site_ids:
1022             for i in range(per_site):
1023                 # Add PCU
1024                 pcu_fields = random_pcu()
1025                 pcu_id = self.api.AddPCU(site_id, pcu_fields)
1026
1027                 # Should return a unique pcu_id
1028                 assert pcu_id not in self.pcu_ids
1029                 self.pcu_ids.append(pcu_id)
1030
1031                 # Add each node at this site to a different port on this PCU
1032                 site = self.api.GetSites([site_id])[0]
1033                 port = randint(1, 10)
1034                 for node_id in site['node_ids']:
1035                     self.api.AddNodeToPCU(node_id, pcu_id, port)
1036                     port += 1
1037
1038                 if self.check:
1039                     # Check PCU
1040                     pcu = self.api.GetPCUs([pcu_id])[0]
1041                     for field in pcu_fields:
1042                         assert pcu[field] == pcu_fields[field]
1043
1044                 if self.verbose:
1045                     print "Added PCU", pcu_id, "to site", site_id
1046
1047     def UpdatePCUs(self):
1048         """
1049         Make random changes to any PCUs we may have added.
1050         """
1051
1052         for pcu_id in self.pcu_ids:
1053             # Update PCU
1054             pcu_fields = random_pcu()
1055             self.api.UpdatePCU(pcu_id, pcu_fields)
1056
1057             if self.check:
1058                 # Check PCU
1059                 pcu = self.api.GetPCUs([pcu_id])[0]
1060                 for field in pcu_fields:
1061                     assert pcu[field] == pcu_fields[field]
1062
1063             if self.verbose:
1064                 print "Updated PCU", pcu_id
1065
1066     def DeletePCUs(self):
1067         """
1068         Delete any random nodes we may have added.
1069         """
1070
1071         for pcu_id in self.pcu_ids:
1072             # Remove nodes from PCU
1073             pcu = self.api.GetPCUs([pcu_id])[0]
1074             for node_id in pcu['node_ids']:
1075                 self.api.DeleteNodeFromPCU(node_id, pcu_id)
1076
1077             if self.check:
1078                 pcu = self.api.GetPCUs([pcu_id])[0]
1079                 assert not pcu['node_ids']
1080
1081             self.api.DeletePCU(pcu_id)
1082
1083             if self.check:
1084                 assert not self.api.GetPCUs([pcu_id])
1085
1086             if self.verbose:
1087                 print "Deleted PCU", pcu_id
1088
1089         if self.check:
1090             assert not self.api.GetPCUs(self.pcu_ids)
1091
1092         self.pcu_ids = []
1093
1094     def AddConfFiles(self, n = 10):
1095         """
1096         Add a number of random global configuration files.
1097         """
1098
1099         conf_files = []
1100
1101         for i in range(n):
1102             # Add a random configuration file
1103             conf_files.append(random_conf_file())
1104
1105         if n:
1106             # Add a nodegroup override file
1107             nodegroup_conf_file = conf_files[0].copy()
1108             nodegroup_conf_file['source'] = randpath(255)
1109             conf_files.append(nodegroup_conf_file)
1110
1111             # Add a node override file
1112             node_conf_file = conf_files[0].copy()
1113             node_conf_file['source'] = randpath(255)
1114             conf_files.append(node_conf_file)
1115
1116         for conf_file_fields in conf_files:
1117             conf_file_id = self.api.AddConfFile(conf_file_fields)
1118
1119             # Should return a unique conf_file_id
1120             assert conf_file_id not in self.conf_file_ids
1121             self.conf_file_ids.append(conf_file_id)
1122
1123             # Add to nodegroup
1124             if conf_file_fields == nodegroup_conf_file and self.nodegroup_ids:
1125                 nodegroup_id = random.sample(self.nodegroup_ids, 1)[0]
1126                 self.api.AddConfFileToNodeGroup(conf_file_id, nodegroup_id)
1127             else:
1128                 nodegroup_id = None
1129
1130             # Add to node
1131             if conf_file_fields == node_conf_file and self.node_ids:
1132                 node_id = random.sample(self.node_ids, 1)[0]
1133                 self.api.AddConfFileToNode(conf_file_id, node_id)
1134             else:
1135                 node_id = None
1136
1137             if self.check:
1138                 # Check configuration file
1139                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1140                 for field in conf_file_fields:
1141                     assert conf_file[field] == conf_file_fields[field]
1142
1143             if self.verbose:
1144                 print "Added configuration file", conf_file_id,
1145                 if nodegroup_id is not None:
1146                     print "to node group", nodegroup_id,
1147                 elif node_id is not None:
1148                     print "to node", node_id,
1149                 print
1150
1151     def UpdateConfFiles(self):
1152         """
1153         Make random changes to any configuration files we may have added.
1154         """
1155
1156         for conf_file_id in self.conf_file_ids:
1157             # Update configuration file
1158             conf_file_fields = random_conf_file()
1159             # Do not update dest so that it remains an override if set
1160             if 'dest' in conf_file_fields:
1161                 del conf_file_fields['dest']
1162             self.api.UpdateConfFile(conf_file_id, conf_file_fields)
1163
1164             if self.check:
1165                 # Check configuration file
1166                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1167                 for field in conf_file_fields:
1168                     assert conf_file[field] == conf_file_fields[field]
1169
1170             if self.verbose:
1171                 print "Updated configuration file", conf_file_id
1172
1173     def DeleteConfFiles(self):
1174         """
1175         Delete any random configuration files we may have added.
1176         """
1177
1178         for conf_file_id in self.conf_file_ids:
1179             self.api.DeleteConfFile(conf_file_id)
1180
1181             if self.check:
1182                 assert not self.api.GetConfFiles([conf_file_id])
1183
1184             if self.verbose:
1185                 print "Deleted configuration file", conf_file_id
1186
1187         if self.check:
1188             assert not self.api.GetConfFiles(self.conf_file_ids)
1189
1190         self.conf_file_ids = []
1191
1192     def AddSliceAttributeTypes(self, n = 10):
1193         """
1194         Add a number of random slice attribute types.
1195         """
1196
1197         roles = self.api.GetRoles()
1198         if not roles:
1199             raise Exception, "No roles"
1200         role_ids = [role['role_id'] for role in roles]
1201
1202         for i in range(n):
1203             attribute_type_fields = random_attribute_type(role_ids)
1204             attribute_type_id = self.api.AddSliceAttributeType(attribute_type_fields)
1205
1206             # Should return a unique attribute_type_id
1207             assert attribute_type_id not in self.attribute_type_ids
1208             self.attribute_type_ids.append(attribute_type_id)
1209
1210             if self.check:
1211                 # Check slice attribute type
1212                 attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
1213                 for field in attribute_type_fields:
1214                     assert attribute_type[field] == attribute_type_fields[field]
1215
1216             if self.verbose:
1217                 print "Added slice attribute type", attribute_type_id
1218
1219     def UpdateSliceAttributeTypes(self):
1220         """
1221         Make random changes to any slice attribute types we may have added.
1222         """
1223
1224         roles = self.api.GetRoles()
1225         if not roles:
1226             raise Exception, "No roles"
1227         role_ids = [role['role_id'] for role in roles]
1228
1229         for attribute_type_id in self.attribute_type_ids:
1230             # Update slice attribute type
1231             attribute_type_fields = random_attribute_type(role_ids)
1232             self.api.UpdateSliceAttributeType(attribute_type_id, attribute_type_fields)
1233
1234             if self.check:
1235                 # Check slice attribute type
1236                 attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
1237                 for field in attribute_type_fields:
1238                     assert attribute_type[field] == attribute_type_fields[field]
1239
1240             if self.verbose:
1241                 print "Updated slice attribute type", attribute_type_id
1242
1243     def DeleteSliceAttributeTypes(self):
1244         """
1245         Delete any random slice attribute types we may have added.
1246         """
1247
1248         for attribute_type_id in self.attribute_type_ids:
1249             self.api.DeleteSliceAttributeType(attribute_type_id)
1250
1251             if self.check:
1252                 assert not self.api.GetSliceAttributeTypes([attribute_type_id])
1253
1254             if self.verbose:
1255                 print "Deleted slice attribute type", attribute_type_id
1256
1257         if self.check:
1258             assert not self.api.GetSliceAttributeTypes(self.attribute_type_ids)
1259
1260         self.attribute_type_ids = []
1261
1262     def AddSlices(self, per_site = 10):
1263         """
1264         Add a number of random slices per site.
1265         """
1266
1267         for site in self.api.GetSites(self.site_ids):
1268             for i in range(min(per_site, site['max_slices'])):
1269                 # Add slice
1270                 slice_fields = random_slice(site['login_base'])
1271                 slice_id = self.api.AddSlice(slice_fields)
1272
1273                 # Should return a unique slice_id
1274                 assert slice_id not in self.slice_ids
1275                 self.slice_ids.append(slice_id)
1276
1277                 # Add slice to a random set of nodes
1278                 node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1279                 if node_ids:
1280                     self.api.AddSliceToNodes(slice_id, node_ids)
1281
1282                 # Add random set of site users to slice
1283                 person_ids = random.sample(site['person_ids'], randint(0, len(site['person_ids'])))
1284                 for person_id in person_ids:
1285                     self.api.AddPersonToSlice(person_id, slice_id)
1286
1287                 if self.check:
1288                     # Check slice
1289                     slice = self.api.GetSlices([slice_id])[0]
1290                     for field in slice_fields:
1291                         assert slice[field] == slice_fields[field]
1292
1293                     assert set(node_ids) == set(slice['node_ids'])
1294                     assert set(person_ids) == set(slice['person_ids'])
1295
1296                 if self.verbose:
1297                     print "Added slice", slice_id, "to site", site['site_id'],
1298                     if node_ids:
1299                         print "and nodes", node_ids,
1300                     print
1301                     if person_ids:
1302                         print "Added users", site['person_ids'], "to slice", slice_id
1303
1304     def UpdateSlices(self):
1305         """
1306         Make random changes to any slices we may have added.
1307         """
1308
1309         for slice_id in self.slice_ids:
1310             # Update slice
1311             slice_fields = random_slice("unused")
1312             # Cannot change slice name
1313             if 'name' in slice_fields:
1314                 del slice_fields['name']
1315             self.api.UpdateSlice(slice_id, slice_fields)
1316
1317             slice = self.api.GetSlices([slice_id])[0]
1318
1319             # Add slice to a random set of nodes
1320             node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1321             self.api.AddSliceToNodes(slice_id, list(set(node_ids) - set(slice['node_ids'])))
1322             self.api.DeleteSliceFromNodes(slice_id, list(set(slice['node_ids']) - set(node_ids)))
1323
1324             # Add random set of users to slice
1325             person_ids = random.sample(self.person_ids, randint(0, len(self.person_ids)))
1326             for person_id in (set(person_ids) - set(slice['person_ids'])):
1327                 self.api.AddPersonToSlice(person_id, slice_id)
1328             for person_id in (set(slice['person_ids']) - set(person_ids)):
1329                 self.api.DeletePersonFromSlice(person_id, slice_id)
1330
1331             if self.check:
1332                 slice = self.api.GetSlices([slice_id])[0]
1333                 for field in slice_fields:
1334                     assert slice[field] == slice_fields[field]
1335                 assert set(node_ids) == set(slice['node_ids'])
1336                 assert set(person_ids) == set(slice['person_ids'])
1337
1338             if self.verbose:
1339                 print "Updated slice", slice_id
1340                 print "Added nodes", node_ids, "to slice", slice_id
1341                 print "Added persons", person_ids, "to slice", slice_id
1342
1343     def DeleteSlices(self):
1344         """
1345         Delete any random slices we may have added.
1346         """
1347
1348         for slice_id in self.slice_ids:
1349             self.api.DeleteSlice(slice_id)
1350
1351             if self.check:
1352                 assert not self.api.GetSlices([slice_id])
1353
1354             if self.verbose:
1355                 print "Deleted slice", slice_id
1356
1357         if self.check:
1358             assert not self.api.GetSlices(self.slice_ids)
1359
1360         self.slice_ids = []
1361
1362     def AddSliceAttributes(self, per_slice = 2):
1363         """
1364         Add a number of random slices per site.
1365         """
1366
1367         if not self.attribute_type_ids:
1368             return
1369
1370         for slice_id in self.slice_ids:
1371             slice = self.api.GetSlices([slice_id])[0]
1372
1373             for i in range(per_slice):
1374                 # Set a random slice/sliver attribute
1375                 for attribute_type_id in random.sample(self.attribute_type_ids, 1):
1376                     value = randstr(16, letters + '_' + digits)
1377                     # Make it a sliver attribute with 50% probability
1378                     if slice['node_ids']:
1379                         node_id = random.sample(slice['node_ids'] + [None] * len(slice['node_ids']), 1)[0]
1380                     else:
1381                         node_id = None
1382
1383                     # Add slice attribute
1384                     if node_id is None:
1385                         slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value)
1386                     else:
1387                         slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value, node_id)
1388
1389                     # Should return a unique slice_attribute_id
1390                     assert slice_attribute_id not in self.slice_attribute_ids
1391                     self.slice_attribute_ids.append(slice_attribute_id)
1392
1393                     if self.check:
1394                         # Check slice attribute
1395                         slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
1396                         for field in 'attribute_type_id', 'slice_id', 'node_id', 'slice_attribute_id', 'value':
1397                             assert slice_attribute[field] == locals()[field]
1398
1399                     if self.verbose:
1400                         print "Added slice attribute", slice_attribute_id, "of type", attribute_type_id,
1401                         if node_id is not None:
1402                             print "to node", node_id,
1403                         print
1404                         
1405     def UpdateSliceAttributes(self):
1406         """
1407         Make random changes to any slice attributes we may have added.
1408         """
1409
1410         for slice_attribute_id in self.slice_attribute_ids:
1411             # Update slice attribute
1412             value = randstr(16, letters + '_' + digits)
1413             self.api.UpdateSliceAttribute(slice_attribute_id, value)
1414
1415             # Check slice attribute again
1416             slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
1417             assert slice_attribute['value'] == value
1418
1419             if self.verbose:
1420                 print "Updated slice attribute", slice_attribute_id
1421
1422     def DeleteSliceAttributes(self):
1423         """
1424         Delete any random slice attributes we may have added.
1425         """
1426
1427         for slice_attribute_id in self.slice_attribute_ids:
1428             self.api.DeleteSliceAttribute(slice_attribute_id)
1429
1430             if self.check:
1431                 assert not self.api.GetSliceAttributes([slice_attribute_id])
1432
1433             if self.verbose:
1434                 print "Deleted slice attribute", slice_attribute_id
1435
1436         if self.check:
1437             assert not self.api.GetSliceAttributes(self.slice_attribute_ids)
1438
1439         self.slice_attribute_ids = []
1440
1441 def main():
1442     parser = OptionParser()
1443     parser.add_option("-c", "--check", action = "store_true", default = False, help = "Check most actions (default: %default)")
1444     parser.add_option("-q", "--quiet", action = "store_true", default = False, help = "Be quiet (default: %default)")
1445     parser.add_option("-t", "--tiny", action = "store_true", default = False, help = "Run a tiny test (default: %default)")
1446     (options, args) = parser.parse_args()
1447
1448     test = Test(api = Shell(),
1449                 check = options.check,
1450                 verbose = not options.quiet)
1451
1452     if options.tiny:
1453         params = Test.tiny
1454     else:
1455         params = Test.default
1456
1457     test.Run(**params)
1458
1459 if __name__ == "__main__":
1460     main()