integrated plcapi test (formerly in PLCAPI/PLC/Test.py) as the plcsh_stress_test...
[tests.git] / system / plcsh-stress-test.py
1 #!/usr/bin/env plcsh
2 #
3 # Test script utility class
4 #
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2006 The Trustees of Princeton University
7 #
8 # $Id$
9 #
10
11 from pprint import pprint
12 from string import letters, digits, punctuation
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 nodegroups_tagname='deployment'
139 def random_nodegroup():
140     return (randstr(50), # groupname
141             nodegroups_tagname,      # tagname
142             randstr(8),  # tagvalue
143             )
144
145 def random_node(boot_states):
146     return {
147         'hostname': randhostname(),
148         'boot_state': random.sample(boot_states, 1)[0],
149         'model': randstr(255),
150         'version': randstr(64),
151         }
152
153 def random_interface(method, type):
154     interface_fields = {
155         'method': method,
156         'type': type,
157         'bwlimit': randint(500000, 10000000),
158         }
159
160     if method != 'dhcp':
161         ip = randint(0, 0xffffffff)
162         netmask = (0xffffffff << randint(2, 31)) & 0xffffffff
163         network = ip & netmask
164         broadcast = ((ip & netmask) | ~netmask) & 0xffffffff
165         gateway = randint(network + 1, broadcast - 1)
166         dns1 = randint(0, 0xffffffff)
167
168         for field in 'ip', 'netmask', 'network', 'broadcast', 'gateway', 'dns1':
169             interface_fields[field] = socket.inet_ntoa(struct.pack('>L', locals()[field]))
170
171     return interface_fields
172
173 def random_pcu():
174     return {
175         'hostname': randhostname(),
176         'ip': socket.inet_ntoa(struct.pack('>L', randint(0, 0xffffffff))),
177         'protocol': randstr(16),
178         'username': randstr(254),
179         'password': randstr(254),
180         'notes': randstr(254),
181         'model': randstr(32),
182         }
183
184 def random_conf_file():
185     return {
186         'enabled': bool(randint()),
187         'source': randpath(255),
188         'dest': randpath(255),
189         'file_permissions': "%#o" % randint(0, 512),
190         'file_owner': randstr(32, letters + '_' + digits),
191         'file_group': randstr(32, letters + '_' + digits),
192         'preinstall_cmd': randpath(100),
193         'postinstall_cmd': randpath(100),
194         'error_cmd': randpath(100),
195         'ignore_cmd_errors': bool(randint()),
196         'always_update': bool(randint()),
197         }
198
199 def random_attribute_type(role_ids):
200     return {
201         'name': randstr(100),
202         'description': randstr(254),
203         'min_role_id': random.sample(role_ids, 1)[0],
204         }
205
206 def random_slice(login_base):
207     return {
208         'name': login_base + "_" + randstr(11, letters).lower(),
209         'url': "http://" + randhostname() + "/",
210         'description': randstr(2048),
211         }
212
213 class Test:
214     tiny = {
215         'sites': 1,
216         'address_types': 1,
217         'addresses_per_site': 1,
218         'persons_per_site': 1,
219         'keys_per_person': 1,
220         'nodegroups': 1,
221         'nodes_per_site': 1,
222         'interfaces_per_node': 1,
223         'pcus_per_site': 1,
224         'conf_files': 1,
225         'attribute_types': 1,
226         'slices_per_site': 1,
227         'attributes_per_slice': 1,
228         }
229
230     default = {
231         'sites': 10,
232         'address_types': 2,
233         'addresses_per_site': 2,
234         'persons_per_site': 10,
235         'keys_per_person': 2,
236         # we're using a single tag so a given node can only be in a single nodegroup
237         'nodegroups': 1,
238         'nodes_per_site': 2,
239         'interfaces_per_node': 1,
240         'pcus_per_site': 1,
241         'conf_files': 10,
242         'attribute_types': 10,
243         'slices_per_site': 10,
244         'attributes_per_slice': 2,
245         }
246
247     def __init__(self, api, check = True, verbose = True):
248         self.api = api
249         self.check = check
250         self.verbose = verbose
251         
252         self.site_ids = []
253         self.address_type_ids = []
254         self.address_ids = []
255         self.person_ids = []
256         self.key_ids = []
257         self.nodegroup_ids = []
258         self.node_ids = []
259         self.interface_ids = []
260         self.pcu_ids = []
261         self.conf_file_ids = []
262         self.attribute_type_ids = []
263         self.slice_ids = []
264         self.slice_attribute_ids = []
265
266     def Run(self, **kwds):
267         """
268         Run a complete database and API consistency test. Populates
269         the database with a set of random entities, updates them, then
270         deletes them. Examples:
271
272         test.Run() # Defaults
273         test.Run(**Test.default) # Defaults
274         test.Run(**Test.tiny) # Tiny set
275         test.Run(sites = 123, slices_per_site = 4) # Defaults with overrides
276         """
277
278         self.Add(**kwds)
279         self.Update()
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.AddInterfaces(params['interfaces_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.UpdateInterfaces()
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.DeleteInterfaces()
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     # this assumes the default node tag types in db-config are visible
773     def AddNodeGroups(self, n = 10):
774         """
775         Add a number of random node groups.
776         """
777
778         for i in range(n):
779             # Add node group
780             (groupname, tagname, tagvalue) = random_nodegroup()
781             nodegroup_id = self.api.AddNodeGroup(groupname, tagname, tagvalue)
782
783             # Should return a unique nodegroup_id
784             assert nodegroup_id not in self.nodegroup_ids
785             self.nodegroup_ids.append(nodegroup_id)
786
787             if self.check:
788                 # Check node group
789                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
790                 for field in nodegroup_fields:
791                     assert nodegroup[field] == nodegroup_fields[field]
792
793             if self.verbose:
794                 print "Added node group", nodegroup_id
795
796     def UpdateNodeGroups(self):
797         """
798         Make random changes to any node groups we may have added.
799         """
800
801         for nodegroup_id in self.nodegroup_ids:
802             # Update nodegroup
803             (groupname, tagname, tagvalue) = random_nodegroup()
804             # cannot change tagname
805             nodegroup_fields = {'groupname':groupname,
806                                 'tagvalue':tagvalue}
807             self.api.UpdateNodeGroup(nodegroup_id, nodegroup_fields)
808
809             if self.check:
810                 # Check nodegroup
811                 nodegroup = self.api.GetNodeGroups([nodegroup_id])[0]
812                 for field in nodegroup_fields:
813                     assert nodegroup[field] == nodegroup_fields[field]
814
815             if self.verbose:
816                 print "Updated node group", nodegroup_id
817
818     def DeleteNodeGroups(self):
819         """
820         Delete any random node groups we may have added.
821         """
822
823         for nodegroup_id in self.nodegroup_ids:
824             self.api.DeleteNodeGroup(nodegroup_id)
825
826             if self.check:
827                 assert not self.api.GetNodeGroups([nodegroup_id])
828
829             if self.verbose:
830                 print "Deleted node group", nodegroup_id
831
832         if self.check:
833             assert not self.api.GetNodeGroups(self.nodegroup_ids)
834
835         self.nodegroup_ids = []
836
837     def AddNodes(self, per_site = 2):
838         """
839         Add a number of random nodes to each site. Each node will also
840         be added to a random node group if AddNodeGroups() was
841         previously run.
842         """
843         
844         boot_states = self.api.GetBootStates()
845         if not boot_states:
846             raise Exception, "No boot states"
847
848         for site_id in self.site_ids:
849             for i in range(per_site):
850                 # Add node
851                 node_fields = random_node(boot_states)
852                 node_id = self.api.AddNode(site_id, node_fields)
853
854                 # Should return a unique node_id
855                 assert node_id not in self.node_ids
856                 self.node_ids.append(node_id)
857
858                 # Add to a random set of node groups
859 #                nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
860                 nodegroup_ids = random.sample(self.nodegroup_ids, len(self.nodegroup_ids))
861                 for nodegroup_id in nodegroup_ids:
862                     tagvalue = self.api.GetNodeGroups([nodegroup_id])[0]['tagvalue']
863                     self.api.AddNodeTag( node_id, nodegroups_tagname, tagvalue )
864
865                 if self.check:
866                     # Check node
867                     node = self.api.GetNodes([node_id])[0]
868                     for field in node_fields:
869                         assert node[field] == node_fields[field]
870
871                 if self.verbose:
872                     print "Added node", node_id
873
874     def UpdateNodes(self):
875         """
876         Make random changes to any nodes we may have added.
877         """
878
879         boot_states = self.api.GetBootStates()
880         if not boot_states:
881             raise Exception, "No boot states"
882
883         for node_id in self.node_ids:
884             # Update node
885             node_fields = random_node(boot_states)
886             self.api.UpdateNode(node_id, node_fields)
887
888             node = self.api.GetNodes([node_id])[0]
889
890             # Add to a random set of node groups
891             nodegroup_ids = random.sample(self.nodegroup_ids, randint(0, len(self.nodegroup_ids)))
892             for nodegroup_id in nodegroup_ids:
893                 tagvalue = self.api.GetNodeGroups([nodegroup_id])[0]['tagvalue']
894                 # with low lvl API, the tag is expected to already exist, so we need to *update* it
895                 print 'DBG node_id',node_id,'nodegroups_tagname',nodegroups_tagname
896                 node_tag = self.api.GetNodeTags( {'node_id':node_id, 'tagname':nodegroups_tagname } )[0]
897                 self.api.UpdateNodeTag(node_tag['node_tag_id'],tagvalue)
898                 print 'DBG node_tag_id',node_tag['node_tag_id']
899
900
901             if self.check:
902                 # Check node
903                 node = self.api.GetNodes([node_id])[0]
904                 for field in node_fields:
905                     assert node[field] == node_fields[field]
906                 assert set(nodegroup_ids) == set(node['nodegroup_ids'])
907
908             if self.verbose:
909                 print "Updated node", node_id
910                 print "Added node", node_id, "to node groups", nodegroup_ids
911
912     def DeleteNodes(self):
913         """
914         Delete any random nodes we may have added.
915         """
916
917         for node_id in self.node_ids:
918             # Remove from node groups
919             node = self.api.GetNodes([node_id])[0]
920             node_tag = GetNodeTags ( {'node_id': node_id, 'tagname': nodegroups_tagname } )[0]
921             self.api.UpdateNodeTag(node_tag['node_tag_id'],'')
922
923             if self.check:
924                 node = self.api.GetNodes([node_id])[0]
925                 assert not node['nodegroup_ids']
926
927             self.api.DeleteNode(node_id)
928
929             if self.check:
930                 assert not self.api.GetNodes([node_id])
931
932             if self.verbose:
933                 print "Deleted node", node_id
934
935         if self.check:
936             assert not self.api.GetNodes(self.node_ids)
937
938         self.node_ids = []
939
940     def AddInterfaces(self, per_node = 1):
941         """
942         Add a number of random network interfaces to each node.
943         """
944
945         network_methods = self.api.GetNetworkMethods()
946         if not network_methods:
947             raise Exception, "No network methods"
948         
949         network_types = self.api.GetNetworkTypes()
950         if not network_types:
951             raise Exception, "No network types"
952
953         for node_id in self.node_ids:
954             for i in range(per_node):
955                 method = random.sample(network_methods, 1)[0]
956                 type = random.sample(network_types, 1)[0]
957
958                 # Add node network
959                 interface_fields = random_interface(method, type)
960                 interface_id = self.api.AddInterface(node_id, interface_fields)
961
962                 # Should return a unique interface_id
963                 assert interface_id not in self.interface_ids
964                 self.interface_ids.append(interface_id)
965
966                 if self.check:
967                     # Check node network
968                     interface = self.api.GetInterfaces([interface_id])[0]
969                     for field in interface_fields:
970                         assert interface[field] == interface_fields[field]
971
972                 if self.verbose:
973                     print "Added node network", interface_id, "to node", node_id
974
975     def UpdateInterfaces(self):
976         """
977         Make random changes to any network interfaces we may have added.
978         """
979
980         network_methods = self.api.GetNetworkMethods()
981         if not network_methods:
982             raise Exception, "No network methods"
983         
984         network_types = self.api.GetNetworkTypes()
985         if not network_types:
986             raise Exception, "No network types"
987
988         for interface_id in self.interface_ids:
989             method = random.sample(network_methods, 1)[0]
990             type = random.sample(network_types, 1)[0]
991
992             # Update interface
993             interface_fields = random_interface(method, type)
994             self.api.UpdateInterface(interface_id, interface_fields)
995
996             if self.check:
997                 # Check interface
998                 interface = self.api.GetInterfaces([interface_id])[0]
999                 for field in interface_fields:
1000                     assert interface[field] == interface_fields[field]
1001
1002             if self.verbose:
1003                 print "Updated node network", interface_id
1004
1005     def DeleteInterfaces(self):
1006         """
1007         Delete any random network interfaces we may have added.
1008         """
1009
1010         for interface_id in self.interface_ids:
1011             self.api.DeleteInterface(interface_id)
1012
1013             if self.check:
1014                 assert not self.api.GetInterfaces([interface_id])
1015
1016             if self.verbose:
1017                 print "Deleted node network", interface_id
1018
1019         if self.check:
1020             assert not self.api.GetInterfaces(self.interface_ids)
1021
1022         self.interface_ids = []
1023
1024     def AddPCUs(self, per_site = 1):
1025         """
1026         Add a number of random PCUs to each site. Each node at the
1027         site will be added to a port on the PCU if AddNodes() was
1028         previously run.
1029         """
1030
1031         for site_id in self.site_ids:
1032             for i in range(per_site):
1033                 # Add PCU
1034                 pcu_fields = random_pcu()
1035                 pcu_id = self.api.AddPCU(site_id, pcu_fields)
1036
1037                 # Should return a unique pcu_id
1038                 assert pcu_id not in self.pcu_ids
1039                 self.pcu_ids.append(pcu_id)
1040
1041                 # Add each node at this site to a different port on this PCU
1042                 site = self.api.GetSites([site_id])[0]
1043                 port = randint(1, 10)
1044                 for node_id in site['node_ids']:
1045                     self.api.AddNodeToPCU(node_id, pcu_id, port)
1046                     port += 1
1047
1048                 if self.check:
1049                     # Check PCU
1050                     pcu = self.api.GetPCUs([pcu_id])[0]
1051                     for field in pcu_fields:
1052                         assert pcu[field] == pcu_fields[field]
1053
1054                 if self.verbose:
1055                     print "Added PCU", pcu_id, "to site", site_id
1056
1057     def UpdatePCUs(self):
1058         """
1059         Make random changes to any PCUs we may have added.
1060         """
1061
1062         for pcu_id in self.pcu_ids:
1063             # Update PCU
1064             pcu_fields = random_pcu()
1065             self.api.UpdatePCU(pcu_id, pcu_fields)
1066
1067             if self.check:
1068                 # Check PCU
1069                 pcu = self.api.GetPCUs([pcu_id])[0]
1070                 for field in pcu_fields:
1071                     assert pcu[field] == pcu_fields[field]
1072
1073             if self.verbose:
1074                 print "Updated PCU", pcu_id
1075
1076     def DeletePCUs(self):
1077         """
1078         Delete any random nodes we may have added.
1079         """
1080
1081         for pcu_id in self.pcu_ids:
1082             # Remove nodes from PCU
1083             pcu = self.api.GetPCUs([pcu_id])[0]
1084             for node_id in pcu['node_ids']:
1085                 self.api.DeleteNodeFromPCU(node_id, pcu_id)
1086
1087             if self.check:
1088                 pcu = self.api.GetPCUs([pcu_id])[0]
1089                 assert not pcu['node_ids']
1090
1091             self.api.DeletePCU(pcu_id)
1092
1093             if self.check:
1094                 assert not self.api.GetPCUs([pcu_id])
1095
1096             if self.verbose:
1097                 print "Deleted PCU", pcu_id
1098
1099         if self.check:
1100             assert not self.api.GetPCUs(self.pcu_ids)
1101
1102         self.pcu_ids = []
1103
1104     def AddConfFiles(self, n = 10):
1105         """
1106         Add a number of random global configuration files.
1107         """
1108
1109         conf_files = []
1110
1111         for i in range(n):
1112             # Add a random configuration file
1113             conf_files.append(random_conf_file())
1114
1115         if n:
1116             # Add a nodegroup override file
1117             nodegroup_conf_file = conf_files[0].copy()
1118             nodegroup_conf_file['source'] = randpath(255)
1119             conf_files.append(nodegroup_conf_file)
1120
1121             # Add a node override file
1122             node_conf_file = conf_files[0].copy()
1123             node_conf_file['source'] = randpath(255)
1124             conf_files.append(node_conf_file)
1125
1126         for conf_file_fields in conf_files:
1127             conf_file_id = self.api.AddConfFile(conf_file_fields)
1128
1129             # Should return a unique conf_file_id
1130             assert conf_file_id not in self.conf_file_ids
1131             self.conf_file_ids.append(conf_file_id)
1132
1133             # Add to nodegroup
1134             if conf_file_fields == nodegroup_conf_file and self.nodegroup_ids:
1135                 nodegroup_id = random.sample(self.nodegroup_ids, 1)[0]
1136                 self.api.AddConfFileToNodeGroup(conf_file_id, nodegroup_id)
1137             else:
1138                 nodegroup_id = None
1139
1140             # Add to node
1141             if conf_file_fields == node_conf_file and self.node_ids:
1142                 node_id = random.sample(self.node_ids, 1)[0]
1143                 self.api.AddConfFileToNode(conf_file_id, node_id)
1144             else:
1145                 node_id = None
1146
1147             if self.check:
1148                 # Check configuration file
1149                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1150                 for field in conf_file_fields:
1151                     assert conf_file[field] == conf_file_fields[field]
1152
1153             if self.verbose:
1154                 print "Added configuration file", conf_file_id,
1155                 if nodegroup_id is not None:
1156                     print "to node group", nodegroup_id,
1157                 elif node_id is not None:
1158                     print "to node", node_id,
1159                 print
1160
1161     def UpdateConfFiles(self):
1162         """
1163         Make random changes to any configuration files we may have added.
1164         """
1165
1166         for conf_file_id in self.conf_file_ids:
1167             # Update configuration file
1168             conf_file_fields = random_conf_file()
1169             # Do not update dest so that it remains an override if set
1170             if 'dest' in conf_file_fields:
1171                 del conf_file_fields['dest']
1172             self.api.UpdateConfFile(conf_file_id, conf_file_fields)
1173
1174             if self.check:
1175                 # Check configuration file
1176                 conf_file = self.api.GetConfFiles([conf_file_id])[0]
1177                 for field in conf_file_fields:
1178                     assert conf_file[field] == conf_file_fields[field]
1179
1180             if self.verbose:
1181                 print "Updated configuration file", conf_file_id
1182
1183     def DeleteConfFiles(self):
1184         """
1185         Delete any random configuration files we may have added.
1186         """
1187
1188         for conf_file_id in self.conf_file_ids:
1189             self.api.DeleteConfFile(conf_file_id)
1190
1191             if self.check:
1192                 assert not self.api.GetConfFiles([conf_file_id])
1193
1194             if self.verbose:
1195                 print "Deleted configuration file", conf_file_id
1196
1197         if self.check:
1198             assert not self.api.GetConfFiles(self.conf_file_ids)
1199
1200         self.conf_file_ids = []
1201
1202     def AddSliceAttributeTypes(self, n = 10):
1203         """
1204         Add a number of random slice attribute types.
1205         """
1206
1207         roles = self.api.GetRoles()
1208         if not roles:
1209             raise Exception, "No roles"
1210         role_ids = [role['role_id'] for role in roles]
1211
1212         for i in range(n):
1213             attribute_type_fields = random_attribute_type(role_ids)
1214             attribute_type_id = self.api.AddSliceAttributeType(attribute_type_fields)
1215
1216             # Should return a unique attribute_type_id
1217             assert attribute_type_id not in self.attribute_type_ids
1218             self.attribute_type_ids.append(attribute_type_id)
1219
1220             if self.check:
1221                 # Check slice attribute type
1222                 attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
1223                 for field in attribute_type_fields:
1224                     assert attribute_type[field] == attribute_type_fields[field]
1225
1226             if self.verbose:
1227                 print "Added slice attribute type", attribute_type_id
1228
1229     def UpdateSliceAttributeTypes(self):
1230         """
1231         Make random changes to any slice attribute types we may have added.
1232         """
1233
1234         roles = self.api.GetRoles()
1235         if not roles:
1236             raise Exception, "No roles"
1237         role_ids = [role['role_id'] for role in roles]
1238
1239         for attribute_type_id in self.attribute_type_ids:
1240             # Update slice attribute type
1241             attribute_type_fields = random_attribute_type(role_ids)
1242             self.api.UpdateSliceAttributeType(attribute_type_id, attribute_type_fields)
1243
1244             if self.check:
1245                 # Check slice attribute type
1246                 attribute_type = self.api.GetSliceAttributeTypes([attribute_type_id])[0]
1247                 for field in attribute_type_fields:
1248                     assert attribute_type[field] == attribute_type_fields[field]
1249
1250             if self.verbose:
1251                 print "Updated slice attribute type", attribute_type_id
1252
1253     def DeleteSliceAttributeTypes(self):
1254         """
1255         Delete any random slice attribute types we may have added.
1256         """
1257
1258         for attribute_type_id in self.attribute_type_ids:
1259             self.api.DeleteSliceAttributeType(attribute_type_id)
1260
1261             if self.check:
1262                 assert not self.api.GetSliceAttributeTypes([attribute_type_id])
1263
1264             if self.verbose:
1265                 print "Deleted slice attribute type", attribute_type_id
1266
1267         if self.check:
1268             assert not self.api.GetSliceAttributeTypes(self.attribute_type_ids)
1269
1270         self.attribute_type_ids = []
1271
1272     def AddSlices(self, per_site = 10):
1273         """
1274         Add a number of random slices per site.
1275         """
1276
1277         for site in self.api.GetSites(self.site_ids):
1278             for i in range(min(per_site, site['max_slices'])):
1279                 # Add slice
1280                 slice_fields = random_slice(site['login_base'])
1281                 slice_id = self.api.AddSlice(slice_fields)
1282
1283                 # Should return a unique slice_id
1284                 assert slice_id not in self.slice_ids
1285                 self.slice_ids.append(slice_id)
1286
1287                 # Add slice to a random set of nodes
1288                 node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1289                 if node_ids:
1290                     self.api.AddSliceToNodes(slice_id, node_ids)
1291
1292                 # Add random set of site users to slice
1293                 person_ids = random.sample(site['person_ids'], randint(0, len(site['person_ids'])))
1294                 for person_id in person_ids:
1295                     self.api.AddPersonToSlice(person_id, slice_id)
1296
1297                 if self.check:
1298                     # Check slice
1299                     slice = self.api.GetSlices([slice_id])[0]
1300                     for field in slice_fields:
1301                         assert slice[field] == slice_fields[field]
1302
1303                     assert set(node_ids) == set(slice['node_ids'])
1304                     assert set(person_ids) == set(slice['person_ids'])
1305
1306                 if self.verbose:
1307                     print "Added slice", slice_id, "to site", site['site_id'],
1308                     if node_ids:
1309                         print "and nodes", node_ids,
1310                     print
1311                     if person_ids:
1312                         print "Added users", site['person_ids'], "to slice", slice_id
1313
1314     def UpdateSlices(self):
1315         """
1316         Make random changes to any slices we may have added.
1317         """
1318
1319         for slice_id in self.slice_ids:
1320             # Update slice
1321             slice_fields = random_slice("unused")
1322             # Cannot change slice name
1323             if 'name' in slice_fields:
1324                 del slice_fields['name']
1325             self.api.UpdateSlice(slice_id, slice_fields)
1326
1327             slice = self.api.GetSlices([slice_id])[0]
1328
1329             # Add slice to a random set of nodes
1330             node_ids = random.sample(self.node_ids, randint(0, len(self.node_ids)))
1331             self.api.AddSliceToNodes(slice_id, list(set(node_ids) - set(slice['node_ids'])))
1332             self.api.DeleteSliceFromNodes(slice_id, list(set(slice['node_ids']) - set(node_ids)))
1333
1334             # Add random set of users to slice
1335             person_ids = random.sample(self.person_ids, randint(0, len(self.person_ids)))
1336             for person_id in (set(person_ids) - set(slice['person_ids'])):
1337                 self.api.AddPersonToSlice(person_id, slice_id)
1338             for person_id in (set(slice['person_ids']) - set(person_ids)):
1339                 self.api.DeletePersonFromSlice(person_id, slice_id)
1340
1341             if self.check:
1342                 slice = self.api.GetSlices([slice_id])[0]
1343                 for field in slice_fields:
1344                     assert slice[field] == slice_fields[field]
1345                 assert set(node_ids) == set(slice['node_ids'])
1346                 assert set(person_ids) == set(slice['person_ids'])
1347
1348             if self.verbose:
1349                 print "Updated slice", slice_id
1350                 print "Added nodes", node_ids, "to slice", slice_id
1351                 print "Added persons", person_ids, "to slice", slice_id
1352
1353     def DeleteSlices(self):
1354         """
1355         Delete any random slices we may have added.
1356         """
1357
1358         for slice_id in self.slice_ids:
1359             self.api.DeleteSlice(slice_id)
1360
1361             if self.check:
1362                 assert not self.api.GetSlices([slice_id])
1363
1364             if self.verbose:
1365                 print "Deleted slice", slice_id
1366
1367         if self.check:
1368             assert not self.api.GetSlices(self.slice_ids)
1369
1370         self.slice_ids = []
1371
1372     def AddSliceAttributes(self, per_slice = 2):
1373         """
1374         Add a number of random slices per site.
1375         """
1376
1377         if not self.attribute_type_ids:
1378             return
1379
1380         for slice_id in self.slice_ids:
1381             slice = self.api.GetSlices([slice_id])[0]
1382
1383             for i in range(per_slice):
1384                 # Set a random slice/sliver attribute
1385                 for attribute_type_id in random.sample(self.attribute_type_ids, 1):
1386                     value = randstr(16, letters + '_' + digits)
1387                     # Make it a sliver attribute with 50% probability
1388                     if slice['node_ids']:
1389                         node_id = random.sample(slice['node_ids'] + [None] * len(slice['node_ids']), 1)[0]
1390                     else:
1391                         node_id = None
1392
1393                     # Add slice attribute
1394                     if node_id is None:
1395                         slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value)
1396                     else:
1397                         slice_attribute_id = self.api.AddSliceAttribute(slice_id, attribute_type_id, value, node_id)
1398
1399                     # Should return a unique slice_attribute_id
1400                     assert slice_attribute_id not in self.slice_attribute_ids
1401                     self.slice_attribute_ids.append(slice_attribute_id)
1402
1403                     if self.check:
1404                         # Check slice attribute
1405                         slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
1406                         for field in 'attribute_type_id', 'slice_id', 'node_id', 'slice_attribute_id', 'value':
1407                             assert slice_attribute[field] == locals()[field]
1408
1409                     if self.verbose:
1410                         print "Added slice attribute", slice_attribute_id, "of type", attribute_type_id,
1411                         if node_id is not None:
1412                             print "to node", node_id,
1413                         print
1414                         
1415     def UpdateSliceAttributes(self):
1416         """
1417         Make random changes to any slice attributes we may have added.
1418         """
1419
1420         for slice_attribute_id in self.slice_attribute_ids:
1421             # Update slice attribute
1422             value = randstr(16, letters + '_' + digits)
1423             self.api.UpdateSliceAttribute(slice_attribute_id, value)
1424
1425             # Check slice attribute again
1426             slice_attribute = self.api.GetSliceAttributes([slice_attribute_id])[0]
1427             assert slice_attribute['value'] == value
1428
1429             if self.verbose:
1430                 print "Updated slice attribute", slice_attribute_id
1431
1432     def DeleteSliceAttributes(self):
1433         """
1434         Delete any random slice attributes we may have added.
1435         """
1436
1437         for slice_attribute_id in self.slice_attribute_ids:
1438             self.api.DeleteSliceAttribute(slice_attribute_id)
1439
1440             if self.check:
1441                 assert not self.api.GetSliceAttributes([slice_attribute_id])
1442
1443             if self.verbose:
1444                 print "Deleted slice attribute", slice_attribute_id
1445
1446         if self.check:
1447             assert not self.api.GetSliceAttributes(self.slice_attribute_ids)
1448
1449         self.slice_attribute_ids = []
1450
1451 def main():
1452     parser = OptionParser()
1453     parser.add_option("-c", "--check", action = "store_true", default = False, 
1454                       help = "Check most actions (default: %default)")
1455     parser.add_option("-q", "--quiet", action = "store_true", default = False, 
1456                       help = "Be quiet (default: %default)")
1457     parser.add_option("-t", "--tiny", action = "store_true", default = False, 
1458                       help = "Run a tiny test (default: %default)")
1459     (options, args) = parser.parse_args()
1460
1461     test = Test(api = Shell(),
1462                 check = options.check,
1463                 verbose = not options.quiet)
1464
1465     if options.tiny:
1466         params = Test.tiny
1467     else:
1468         params = Test.default
1469
1470     test.Run(**params)
1471
1472 if __name__ == "__main__":
1473     main()