Default to a NULL administrator, instead of the same as the owner
[invirt/packages/invirt-web.git] / code / controls.py
1 import validation
2 from invirt.common import CodeError, InvalidInput
3 import random
4 import sys
5 import time
6 import re
7 import cache_acls
8 import yaml
9
10 from invirt.config import structs as config
11 from invirt.database import Machine, Disk, Type, NIC, CDROM, session, meta
12 from invirt.remctl import remctl as gen_remctl
13
14 # ... and stolen from xend/uuid.py
15 def randomUUID():
16     """Generate a random UUID."""
17
18     return [ random.randint(0, 255) for _ in range(0, 16) ]
19
20 def uuidToString(u):
21     """Turn a numeric UUID to a hyphen-seperated one."""
22     return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
23                      "%02x" * 6]) % tuple(u)
24 # end stolen code
25
26 def remctl(*args, **kwargs):
27     return gen_remctl(config.remote.hostname,
28                       principal='daemon/'+config.web.hostname,
29                       *args, **kwargs)
30
31 def lvcreate(machine, disk):
32     """Create a single disk for a machine"""
33     remctl('web', 'lvcreate', machine.name,
34            disk.guest_device_name, str(disk.size))
35     
36 def makeDisks(machine):
37     """Update the lvm partitions to add a disk."""
38     for disk in machine.disks:
39         lvcreate(machine, disk)
40
41 def getswap(disksize, memsize):
42     """Returns the recommended swap partition size."""
43     return int(min(disksize / 4, memsize * 1.5))
44
45 def lvinstall(machine, autoinstall):
46     #raise InvalidInput('autoinstall', 'install',
47     #                   "The autoinstaller has been temporarily disabled")
48     disksize = machine.disks[0].size
49     memsize = machine.memory
50     swapsize = getswap(disksize, memsize)
51     imagesize = disksize - swapsize
52     ip = machine.nics[0].ip
53     remctl('control', machine.name, 'install', 
54            'dist=%s' % autoinstall.distribution,
55            'mirror=%s' % autoinstall.mirror,
56            'arch=%s' % autoinstall.arch,
57            'imagesize=%s' % imagesize)
58
59 def lvcopy(machine_orig_name, machine, rootpw):
60     """Copy a golden image onto a machine's disk"""
61     remctl('web', 'lvcopy', machine_orig_name, machine.name, rootpw)
62
63 def bootMachine(machine, cdtype):
64     """Boot a machine with a given boot CD.
65
66     If cdtype is None, give no boot cd.  Otherwise, it is the string
67     id of the CD (e.g. 'gutsy_i386')
68     """
69     if cdtype is not None:
70         out, err = remctl('control', machine.name, 'create', 
71                           cdtype, err=True)
72     else:
73         out, err = remctl('control', machine.name, 'create',
74                           err=True)
75     if 'already running' in err:
76         raise InvalidInput('action', 'create',
77                            'VM %s is already on' % machine.name)
78     elif err:
79         raise CodeError('"%s" on "control %s create %s' 
80                         % (err, machine.name, cdtype))
81
82 def createVm(username, state, owner, contact, name, description, memory, disksize, machine_type, cdrom, autoinstall):
83     """Create a VM and put it in the database"""
84     # put stuff in the table
85     session.begin()
86     try:
87         validation.Validate(username, state, name=name, description=description, owner=owner, memory=memory, disksize=disksize/1024.)
88         machine = Machine()
89         machine.name = name
90         machine.description = description
91         machine.memory = memory
92         machine.owner = owner
93         machine.administrator = None
94         machine.contact = contact
95         machine.uuid = uuidToString(randomUUID())
96         machine.boot_off_cd = True
97         machine.type = machine_type
98         session.save_or_update(machine)
99         disk = Disk(machine=machine,
100                     guest_device_name='hda', size=disksize)
101         nic = NIC.query().filter_by(machine_id=None).first()
102         if not nic: #No IPs left!
103             raise CodeError("No IP addresses left!  "
104                             "Contact %s." % config.web.errormail)
105         nic.machine = machine
106         nic.hostname = name
107         session.save_or_update(nic)
108         session.save_or_update(disk)
109         cache_acls.refreshMachine(machine)
110         session.commit()
111     except:
112         session.rollback()
113         raise
114     makeDisks(machine)
115     if autoinstall:
116         lvinstall(machine, autoinstall)
117     else:
118         # tell it to boot with cdrom
119         bootMachine(machine, cdrom)
120     return machine
121
122 def getList():
123     """Return a dictionary mapping machine names to dicts."""
124     value_string = remctl('web', 'listvms')
125     value_dict = yaml.load(value_string, yaml.CSafeLoader)
126     return value_dict
127
128 def parseStatus(s):
129     """Parse a status string into nested tuples of strings.
130
131     s = output of xm list --long <machine_name>
132     """
133     values = re.split('([()])', s)
134     stack = [[]]
135     for v in values[2:-2]: #remove initial and final '()'
136         if not v:
137             continue
138         v = v.strip()
139         if v == '(':
140             stack.append([])
141         elif v == ')':
142             if len(stack[-1]) == 1:
143                 stack[-1].append('')
144             stack[-2].append(stack[-1])
145             stack.pop()
146         else:
147             if not v:
148                 continue
149             stack[-1].extend(v.split())
150     return stack[-1]
151
152 def statusInfo(machine):
153     """Return the status list for a given machine.
154
155     Gets and parses xm list --long
156     """
157     value_string, err_string = remctl('control', machine.name, 'list-long', 
158                                       err=True)
159     if 'Unknown command' in err_string:
160         raise CodeError("ERROR in remctl list-long %s is not registered" % 
161                         (machine.name,))
162     elif 'is not on' in err_string:
163         return None
164     elif err_string:
165         raise CodeError("ERROR in remctl list-long %s:  %s" % 
166                         (machine.name, err_string))
167     status = parseStatus(value_string)
168     return status
169
170 def listHost(machine):
171     """Return the host a machine is running on"""
172     out, err = remctl('control', machine.name, 'listhost', err=True)
173     if err:
174         return None
175     return out.strip()
176
177 def vnctoken(machine):
178     """Return a time-stamped VNC token"""
179     out, err = remctl('control', machine.name, 'vnctoken', err=True)
180     if err:
181         return None
182     return out.strip()
183
184 def deleteVM(machine):
185     """Delete a VM."""
186     remctl('control', machine.name, 'destroy', err=True)
187     session.begin()
188     delete_disk_pairs = [(machine.name, d.guest_device_name) 
189                          for d in machine.disks]
190     try:
191         for mname, dname in delete_disk_pairs:
192             remctl('web', 'lvremove', mname, dname)
193         for nic in machine.nics:
194             nic.machine_id = None
195             nic.hostname = None
196             session.save_or_update(nic)
197         for disk in machine.disks:
198             session.delete(disk)
199         session.delete(machine)
200         session.commit()
201     except:
202         session.rollback()
203         raise
204
205 def commandResult(username, state, fields):
206     start_time = 0
207     machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
208     action = fields.getfirst('action')
209     cdrom = fields.getfirst('cdrom')
210     if cdrom is not None and not CDROM.query().filter_by(cdrom_id=cdrom).one():
211         raise CodeError("Invalid cdrom type '%s'" % cdrom)    
212     if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 
213                       'Delete VM'):
214         raise CodeError("Invalid action '%s'" % action)
215     if action == 'Reboot':
216         if cdrom is not None:
217             out, err = remctl('control', machine.name, 'reboot', cdrom,
218                               err=True)
219         else:
220             out, err = remctl('control', machine.name, 'reboot',
221                               err=True)
222         if err:
223             if re.match("machine '.*' is not on", err):
224                 raise InvalidInput("action", "reboot", 
225                                    "Machine is not on")
226             else:
227                 print >> sys.stderr, 'Error on reboot:'
228                 print >> sys.stderr, err
229                 raise CodeError('ERROR on remctl')
230                 
231     elif action == 'Power on':
232         if validation.maxMemory(username, state, machine) < machine.memory:
233             raise InvalidInput('action', 'Power on',
234                                "You don't have enough free RAM quota "
235                                "to turn on this machine.")
236         bootMachine(machine, cdrom)
237     elif action == 'Power off':
238         out, err = remctl('control', machine.name, 'destroy', err=True)
239         if err:
240             if re.match("machine '.*' is not on", err):
241                 raise InvalidInput("action", "Power off", 
242                                    "Machine is not on.")
243             else:
244                 print >> sys.stderr, 'Error on power off:'
245                 print >> sys.stderr, err
246                 raise CodeError('ERROR on remctl')
247     elif action == 'Shutdown':
248         out, err = remctl('control', machine.name, 'shutdown', err=True)
249         if err:
250             if re.match("machine '.*' is not on", err):
251                 raise InvalidInput("action", "Shutdown", 
252                                    "Machine is not on.")
253             else:
254                 print >> sys.stderr, 'Error on Shutdown:'
255                 print >> sys.stderr, err
256                 raise CodeError('ERROR on remctl')
257     elif action == 'Delete VM':
258         deleteVM(machine)
259
260     d = dict(user=username,
261              command=action,
262              machine=machine)
263     return d
264
265 def resizeDisk(machine_name, disk_name, new_size):
266     remctl("web", "lvresize", machine_name, disk_name, new_size)
267
268 def renameMachine(machine, old_name, new_name):
269     for disk in machine.disks:
270         remctl("web", "lvrename", old_name, 
271                disk.guest_device_name, new_name)
272