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