def _started(self, what, notify=None): """ This is the second step:: Wait for the StartNotify thread to finish and then check for the status of pidfile/procname using pgrep Returns: True whether the service is alive, False otherwise """ if what in self.SERVICE_DEFS: procname, pidfile = self.SERVICE_DEFS[what] if notify: notify.join() if pidfile: pgrep = "/bin/pgrep -F {}{}".format( pidfile, ' ' + procname if procname else '', ) else: pgrep = "/bin/pgrep {}".format(procname) proc = Popen(pgrep, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True) data = proc.communicate()[0] if proc.returncode == 0: return True, [ int(i) for i in data.strip().split('\n') if i.isdigit() ] return False, []
def get_data(self, data): """ Get data points from rrd files. """ rrdfile = '{}/{}/{}.rrd'.format(RRD_PATH, data['source'], data['type']) proc = Popen( [ 'rrdtool', 'xport', '--json', '--start', data['start'], '--end', data['end'], ] + (['--step', str(data['step'])] if data.get('step') else []) + [ 'DEF:xxx={}:{}:{}'.format(rrdfile, data['dataset'], data['cf']), 'XPORT:xxx', ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) data, err = proc.communicate() if proc.returncode != 0: raise ValueError('rrdtool failed: {}'.format(err)) return json.loads(data)
def sync(self): domain = None nameservers = [] if self.middleware.call('notifier.common', 'system', 'domaincontroller_enabled'): cifs = self.middlware.call('datastore.query', 'services.cifs', None, {'get': True}) dc = self.middleware.call('datastore.query', 'services.DomainController', None, {'get': True}) domain = dc['dc_realm'] if cifs['cifs_srv_bindip']: for ip in cifs['cifs_srv_bindip']: nameservers.append(ip) else: nameservers.append('127.0.0.1') else: gc = self.middleware.call('datastore.query', 'network.globalconfiguration', None, {'get': True}) if gc['gc_domain']: domain = gc['gc_domain'] if gc['gc_nameserver1']: nameservers.append(gc['gc_nameserver1']) if gc['gc_nameserver2']: nameservers.append(gc['gc_nameserver2']) if gc['gc_nameserver3']: nameservers.append(gc['gc_nameserver3']) resolvconf = '' if domain: resolvconf += 'search {}\n'.format(domain) for ns in nameservers: resolvconf += 'nameserver {}\n'.format(ns) proc = Popen([ '/sbin/resolvconf', '-a', 'lo0' ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate(input=resolvconf)
def sync(self): domain = None nameservers = [] if self.middleware.call('notifier.common', 'system', 'domaincontroller_enabled'): cifs = self.middleware.call('datastore.query', 'services.cifs', None, {'get': True}) dc = self.middleware.call('datastore.query', 'services.DomainController', None, {'get': True}) domain = dc['dc_realm'] if cifs['cifs_srv_bindip']: for ip in cifs['cifs_srv_bindip']: nameservers.append(ip) else: nameservers.append('127.0.0.1') else: gc = self.middleware.call('datastore.query', 'network.globalconfiguration', None, {'get': True}) if gc['gc_domain']: domain = gc['gc_domain'] if gc['gc_nameserver1']: nameservers.append(gc['gc_nameserver1']) if gc['gc_nameserver2']: nameservers.append(gc['gc_nameserver2']) if gc['gc_nameserver3']: nameservers.append(gc['gc_nameserver3']) resolvconf = '' if domain: resolvconf += 'search {}\n'.format(domain) for ns in nameservers: resolvconf += 'nameserver {}\n'.format(ns) proc = Popen([ '/sbin/resolvconf', '-a', 'lo0' ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.communicate(input=resolvconf)
def get_dataset_info(self, source, name): """ Returns info about a given dataset from some source. """ rrdfile = '{}/{}/{}.rrd'.format(RRD_PATH, source, name) proc = Popen( ['rrdtool', 'info', rrdfile], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) data, err = proc.communicate() if proc.returncode != 0: raise ValueError('rrdtool failed: {}'.format(err)) info = {'data_sources': {}} for data_source, _type in RE_DSTYPE.findall(data): info['data_sources'][data_source] = {'type': _type} reg = RE_STEP.search(data) if reg: info['step'] = int(reg.group(1)) reg = RE_LAST_UPDATE.search(data) if reg: info['last_update'] = int(reg.group(1)) return info
def dhclient_start(self, interface): proc = Popen([ '/sbin/dhclient', '-b', interface, ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) output = proc.communicate()[0] if proc.returncode != 0: self.logger.error('Failed to run dhclient on {}: {}'.format( interface, output, ))
def run(self, queue): """ Run a Job and set state/result accordingly. This method is supposed to run in a greenlet. """ try: self.set_state('RUNNING') """ If job is flagged as process a new process is spawned with the job id which will in turn run the method and return the result as a json """ if self.options.get('process'): proc = Popen([ '/usr/bin/env', 'python3', os.path.join( os.path.dirname(os.path.realpath(__file__)), 'job_process.py', ), str(self.id), ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, env={ 'LOGNAME': 'root', 'USER': '******', 'GROUP': 'wheel', 'HOME': '/root', 'PATH': '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin', 'TERM': 'xterm', }) output = proc.communicate() try: data = json.loads(output[0]) except ValueError: self.set_state('FAILED') self.error = 'Running job has failed.\nSTDOUT: {}\nSTDERR: {}'.format(output[0], output[1]) else: if proc.returncode != 0: self.set_state('FAILED') self.error = data['error'] self.exception = data['exception'] else: self.set_result(data) self.set_state('SUCCESS') else: self.set_result(self.method(*([self] + self.args))) self.set_state('SUCCESS') except: self.set_state('FAILED') self.set_exception(sys.exc_info()) raise finally: queue.release_lock(self) self._finished.set() self.middleware.send_event('core.get_jobs', 'CHANGED', id=self.id, fields=self.__encode__())
def ssh_keyscan(self, host, port): proc = Popen( ["/usr/bin/ssh-keyscan", "-p", str(port), "-T", "2", str(host)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) key, errmsg = proc.communicate() if proc.returncode != 0 or not key: if not errmsg: errmsg = "ssh key scan failed for unknown reason" raise ValueError(errmsg) return key
def ssh_keyscan(self, host, port): proc = Popen([ "/usr/bin/ssh-keyscan", "-p", str(port), "-T", "2", str(host), ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) key, errmsg = proc.communicate() if proc.returncode != 0 or not key: if not errmsg: errmsg = 'ssh key scan failed for unknown reason' raise ValueError(errmsg) return key
def _system(self, cmd, options=None): stdout = PIPE if options and 'stdout' in options: stdout = options['stdout'] stderr = PIPE if options and 'stderr' in options: stderr = options['stderr'] proc = Popen(cmd, stdout=stdout, stderr=stderr, shell=True, close_fds=True) proc.communicate() return proc.returncode
def info(self): """ Returns basic system information. """ uptime = Popen( "env -u TZ uptime | awk -F', load averages:' '{ print $1 }'", stdout=subprocess.PIPE, shell=True, ).communicate()[0].strip() return { 'version': self.version(), 'hostname': socket.gethostname(), 'physmem': sysctl.filter('hw.physmem')[0].value, 'model': sysctl.filter('hw.model')[0].value, 'loadavg': os.getloadavg(), 'uptime': uptime, 'boottime': datetime.fromtimestamp( struct.unpack('l', sysctl.filter('kern.boottime')[0].value[:8])[0]), }
def reboot(self, job, options=None): """ Reboots the operating system. Emits an "added" event of name "system" and id "reboot". """ if options is None: options = {} self.middleware.send_event('system', 'ADDED', id='reboot', fields={ 'description': 'System is going to reboot', }) delay = options.get('delay') if delay: time.sleep(delay) Popen(["/sbin/reboot"])
def shutdown(self, job, options=None): """ Shuts down the operating system. Emits an "added" event of name "system" and id "shutdown". """ if options is None: options = {} self.middleware.send_event('system', 'ADDED', id='shutdown', fields={ 'description': 'System is going to shutdown', }) delay = options.get('delay') if delay: time.sleep(delay) Popen(["/sbin/poweroff"])
def sync(self, job, backup, credential): # Use a temporary file to store s3cmd config file with tempfile.NamedTemporaryFile() as f: # Make sure only root can read it ad there is sensitive data os.chmod(f.name, 0o600) fg = gevent.fileobject.FileObject(f.file, 'w', close=False) fg.write("""[remote] type = s3 env_auth = false access_key_id = {access_key} secret_access_key = {secret_key} region = {region} """.format( access_key=credential['attributes']['access_key'], secret_key=credential['attributes']['secret_key'], region=backup['attributes']['region'] or '', )) fg.flush() args = [ '/usr/local/bin/rclone', '--config', f.name, '--stats', '1s', 'sync', ] remote_path = 'remote:{}{}'.format( backup['attributes']['bucket'], '/{}'.format(backup['attributes']['folder']) if backup['attributes'].get('folder') else '', ) if backup['direction'] == 'PUSH': args.extend([backup['path'], remote_path]) else: args.extend([remote_path, backup['path']]) def check_progress(job, proc): RE_TRANSF = re.compile(r'Transferred:\s*?(.+)$', re.S) read_buffer = '' while True: read = proc.stderr.readline() if read == '': break read_buffer += read if len(read_buffer) > 10240: read_buffer = read_buffer[-10240:] reg = RE_TRANSF.search(read) if reg: transferred = reg.group(1).strip() if not transferred.isdigit(): job.set_progress(None, transferred) return read_buffer proc = Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) check_greenlet = gevent.spawn(check_progress, job, proc) proc.communicate() if proc.returncode != 0: gevent.joinall([check_greenlet]) raise ValueError('rclone failed: {}'.format(check_greenlet.value)) return True
def _system(self, cmd): proc = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, close_fds=True) proc.communicate() return proc.returncode
def sync_interface(self, name): try: data = self.middleware.call('datastore.query', 'network.interfaces', [('int_interface', '=', name)], {'get': True}) except IndexError: self.logger.info('{} is not in interfaces database'.format(name)) return aliases = self.middleware.call( 'datastore.query', 'network.alias', [('alias_interface_id', '=', data['id'])]) iface = netif.get_interface(name) addrs_database = set() addrs_configured = set( [a for a in iface.addresses if a.af != netif.AddressFamily.LINK]) has_ipv6 = data['int_ipv6auto'] or False if (not self.middleware.call('system.is_freenas') and self.middleware.call('notifier.failover_node') == 'B'): ipv4_field = 'int_ipv4address_b' ipv6_field = 'int_ipv6address' alias_ipv4_field = 'alias_v4address_b' alias_ipv6_field = 'alias_v6address_b' else: ipv4_field = 'int_ipv4address' ipv6_field = 'int_ipv6address' alias_ipv4_field = 'alias_v4address' alias_ipv6_field = 'alias_v6address' dhclient_running, dhclient_pid = dhclient_status(name) if dhclient_running and data['int_dhcp']: leases = dhclient_leases(name) if leases: reg_address = re.search(r'fixed-address\s+(.+);', leases) reg_netmask = re.search(r'option subnet-mask\s+(.+);', leases) if reg_address and reg_netmask: addrs_database.add( self.alias_to_addr({ 'address': reg_address.group(1), 'netmask': reg_netmask.group(1), })) else: self.logger.info('Unable to get address from dhclient') if data[ipv6_field] and has_ipv6 is False: addrs_database.add( self.alias_to_addr({ 'address': data[ipv6_field], 'netmask': data['int_v6netmaskbit'], })) else: if data[ipv4_field]: addrs_database.add( self.alias_to_addr({ 'address': data[ipv4_field], 'netmask': data['int_v4netmaskbit'], })) if data[ipv6_field] and has_ipv6 is False: addrs_database.add( self.alias_to_addr({ 'address': data[ipv6_field], 'netmask': data['int_v6netmaskbit'], })) has_ipv6 = True carp_vhid = carp_pass = None if data['int_vip']: addrs_database.add( self.alias_to_addr({ 'address': data['int_vip'], 'netmask': '32', 'vhid': data['int_vhid'], })) carp_vhid = data['int_vhid'] carp_pass = data['int_pass'] or None for alias in aliases: if alias[alias_ipv4_field]: addrs_database.add( self.alias_to_addr({ 'address': alias[alias_ipv4_field], 'netmask': alias['alias_v4netmaskbit'], })) if alias[alias_ipv6_field]: addrs_database.add( self.alias_to_addr({ 'address': alias[alias_ipv6_field], 'netmask': alias['alias_v6netmaskbit'], })) if alias['alias_vip']: addrs_database.add( self.alias_to_addr({ 'address': alias['alias_vip'], 'netmask': '32', 'vhid': data['int_vhid'], })) if carp_vhid: advskew = None for cc in iface.carp_config: if cc.vhid == carp_vhid: advskew = cc.advskew break if has_ipv6: iface.nd6_flags = iface.nd6_flags - { netif.NeighborDiscoveryFlags.IFDISABLED } iface.nd6_flags = iface.nd6_flags | { netif.NeighborDiscoveryFlags.AUTO_LINKLOCAL } else: iface.nd6_flags = iface.nd6_flags | { netif.NeighborDiscoveryFlags.IFDISABLED } iface.nd6_flags = iface.nd6_flags - { netif.NeighborDiscoveryFlags.AUTO_LINKLOCAL } # Remove addresses configured and not in database for addr in (addrs_configured - addrs_database): if has_ipv6 and str(addr.address).startswith('fe80::'): continue self.logger.debug('{}: removing {}'.format(name, addr)) iface.remove_address(addr) # carp must be configured after removing addresses # in case removing the address removes the carp if carp_vhid: if not self.middleware.call('notifier.is_freenas') and not advskew: if self.middleware.call('notifier.failover_node') == 'A': advskew = 20 else: advskew = 80 iface.carp_config = [ netif.CarpConfig(carp_vhid, advskew=advskew, key=carp_pass) ] # Add addresses in database and not configured for addr in (addrs_database - addrs_configured): self.logger.debug('{}: adding {}'.format(name, addr)) iface.add_address(addr) # Apply interface options specified in GUI if data['int_options']: self.logger.info('{}: applying {}'.format(name, data['int_options'])) proc = Popen('/sbin/ifconfig {} {}'.format(name, data['int_options']), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) err = proc.communicate()[1] if err: self.logger.info('{}: error applying: {}'.format(name, err)) # In case there is no MTU in interface options and it is currently # different than the default of 1500, revert it if data['int_options'].find('mtu') == -1 and iface.mtu != 1500: iface.mtu = 1500 if netif.InterfaceFlags.UP not in iface.flags: iface.up() # If dhclient is not running and dhcp is configured, lets start it if not dhclient_running and data['int_dhcp']: self.logger.debug('Starting dhclient for {}'.format(name)) gevent.spawn(self.dhclient_start, data['int_interface']) elif dhclient_running and not data['int_dhcp']: self.logger.debug('Killing dhclient for {}'.format(name)) os.kill(dhclient_pid, signal.SIGTERM) if data['int_ipv6auto']: iface.nd6_flags = iface.nd6_flags | { netif.NeighborDiscoveryFlags.ACCEPT_RTADV } Popen( ['/etc/rc.d/rtsold', 'onestart'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, ).wait() else: iface.nd6_flags = iface.nd6_flags - { netif.NeighborDiscoveryFlags.ACCEPT_RTADV }
def sync_interface(self, name): try: data = self.middleware.call('datastore.query', 'network.interfaces', [('int_interface', '=', name)], {'get': True}) except IndexError: self.logger.info('{} is not in interfaces database'.format(name)) return aliases = self.middleware.call('datastore.query', 'network.alias', [('alias_interface_id', '=', data['id'])]) iface = netif.get_interface(name) addrs_database = set() addrs_configured = set([ a for a in iface.addresses if a.af != netif.AddressFamily.LINK ]) has_ipv6 = data['int_ipv6auto'] or False if ( not self.middleware.call('system.is_freenas') and self.middleware.call('notifier.failover_node') == 'B' ): ipv4_field = 'int_ipv4address_b' ipv6_field = 'int_ipv6address' alias_ipv4_field = 'alias_v4address_b' alias_ipv6_field = 'alias_v6address_b' else: ipv4_field = 'int_ipv4address' ipv6_field = 'int_ipv6address' alias_ipv4_field = 'alias_v4address' alias_ipv6_field = 'alias_v6address' dhclient_running, dhclient_pid = dhclient_status(name) if dhclient_running and data['int_dhcp']: leases = dhclient_leases(name) if leases: reg_address = re.search(r'fixed-address\s+(.+);', leases) reg_netmask = re.search(r'option subnet-mask\s+(.+);', leases) if reg_address and reg_netmask: addrs_database.add(self.alias_to_addr({ 'address': reg_address.group(1), 'netmask': reg_netmask.group(1), })) else: self.logger.info('Unable to get address from dhclient') else: if data[ipv4_field]: addrs_database.add(self.alias_to_addr({ 'address': data[ipv4_field], 'netmask': data['int_v4netmaskbit'], })) if data[ipv6_field]: addrs_database.add(self.alias_to_addr({ 'address': data[ipv6_field], 'netmask': data['int_v6netmaskbit'], })) has_ipv6 = True carp_vhid = carp_pass = None if data['int_vip']: addrs_database.add(self.alias_to_addr({ 'address': data['int_vip'], 'netmask': '32', 'vhid': data['int_vhid'], })) carp_vhid = data['int_vhid'] carp_pass = data['int_pass'] or None for alias in aliases: if alias[alias_ipv4_field]: addrs_database.add(self.alias_to_addr({ 'address': alias[alias_ipv4_field], 'netmask': alias['alias_v4netmaskbit'], })) if alias[alias_ipv6_field]: addrs_database.add(self.alias_to_addr({ 'address': alias[alias_ipv6_field], 'netmask': alias['alias_v6netmaskbit'], })) has_ipv6 = True if alias['alias_vip']: addrs_database.add(self.alias_to_addr({ 'address': alias['alias_vip'], 'netmask': '32', 'vhid': data['int_vhid'], })) if has_ipv6: iface.nd6_flags = iface.nd6_flags - {netif.NeighborDiscoveryFlags.IFDISABLED} iface.nd6_flags = iface.nd6_flags | {netif.NeighborDiscoveryFlags.AUTO_LINKLOCAL} else: iface.nd6_flags = iface.nd6_flags | {netif.NeighborDiscoveryFlags.IFDISABLED} iface.nd6_flags = iface.nd6_flags - {netif.NeighborDiscoveryFlags.AUTO_LINKLOCAL} # Remove addresses configured and not in database for addr in (addrs_configured - addrs_database): if ( addr.af == netif.AddressFamily.INET6 and str(addr.address).startswith('fe80::') ): continue self.logger.debug('{}: removing {}'.format(name, addr)) iface.remove_address(addr) # carp must be configured after removing addresses # in case removing the address removes the carp if carp_vhid: advskew = None if not self.middleware.call('notifier.is_freenas'): if self.middleware.call('notifier.failover_status') == 'MASTER': advskew = 20 else: advskew = 80 iface.carp_config = [netif.CarpConfig(carp_vhid, advskew=advskew, key=carp_pass)] # Add addresses in database and not configured for addr in (addrs_database - addrs_configured): self.logger.debug('{}: adding {}'.format(name, addr)) iface.add_address(addr) # Apply interface options specified in GUI if data['int_options']: self.logger.info('{}: applying {}'.format(name, data['int_options'])) proc = Popen('/sbin/ifconfig {} {}'.format(name, data['int_options']), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) err = proc.communicate()[1] if err: self.logger.info('{}: error applying: {}'.format(name, err)) # In case there is no MTU in interface options and it is currently # different than the default of 1500, revert it if data['int_options'].find('mtu') == -1 and iface.mtu != 1500: iface.mtu = 1500 if netif.InterfaceFlags.UP not in iface.flags: iface.up() # If dhclient is not running and dhcp is configured, lets start it if not dhclient_running and data['int_dhcp']: self.logger.debug('Starting dhclient for {}'.format(name)) gevent.spawn(self.dhclient_start, data['int_interface']) elif dhclient_running and not data['int_dhcp']: self.logger.debug('Killing dhclient for {}'.format(name)) os.kill(dhclient_pid, signal.SIGTERM) if data['int_ipv6auto']: iface.nd6_flags = iface.nd6_flags | {netif.NeighborDiscoveryFlags.ACCEPT_RTADV} Popen( ['/etc/rc.d/rtsold', 'onestart'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, ).wait() else: iface.nd6_flags = iface.nd6_flags - {netif.NeighborDiscoveryFlags.ACCEPT_RTADV}
def kmod_load(): kldstat = Popen(['/sbin/kldstat'], stdout=subprocess.PIPE).communicate()[0] if 'vmm.ko' not in kldstat: Popen(['/sbin/kldload', 'vmm']) if 'nmdm.ko' not in kldstat: Popen(['/sbin/kldload', 'nmdm'])
def run(self): args = [ 'bhyve', '-A', '-P', '-H', '-c', str(self.vm['vcpus']), '-m', str(self.vm['memory']), '-s', '0:0,hostbridge', '-s', '31,lpc', '-l', 'com1,/dev/nmdm{}A'.format(self.vm['id']), ] if self.vm['bootloader'] in ('UEFI', 'UEFI_CSM'): args += [ '-l', 'bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI{}.fd'.format('_CSM' if self.vm['bootloader'] == 'UEFI_CSM' else ''), ] nid = Nid(3) for device in self.vm['devices']: if device['dtype'] == 'DISK': if device['attributes'].get('mode') == 'AHCI': args += ['-s', '{},ahci-hd,{}'.format(nid(), device['attributes']['path'])] else: args += ['-s', '{},virtio-blk,{}'.format(nid(), device['attributes']['path'])] elif device['dtype'] == 'CDROM': args += ['-s', '{},ahci-cd,{}'.format(nid(), device['attributes']['path'])] elif device['dtype'] == 'NIC': tapname = netif.create_interface('tap') tap = netif.get_interface(tapname) tap.up() self.taps.append(tapname) # If Bridge if True: bridge = None for name, iface in netif.list_interfaces().items(): if name.startswith('bridge'): bridge = iface break if not bridge: bridge = netif.get_interface(netif.create_interface('bridge')) bridge.add_member(tapname) defiface = Popen("route -nv show default|grep -w interface|awk '{ print $2 }'", stdout=subprocess.PIPE, shell=True).communicate()[0].strip() if defiface and defiface not in bridge.members: bridge.add_member(defiface) bridge.up() if device['attributes'].get('type') == 'VIRTIO': nictype = 'virtio-net' else: nictype = 'e1000' args += ['-s', '{},{},{}'.format(nid(), nictype, tapname)] elif device['dtype'] == 'VNC': if device['attributes'].get('wait'): wait = 'wait' else: wait = '' args += [ '-s', '29,fbuf,tcp=0.0.0.0:{},w=1024,h=768,{}'.format(5900 + self.vm['id'], wait), '-s', '30,xhci,tablet', ] args.append(self.vm['name']) self.logger.debug('Starting bhyve: {}'.format(' '.join(args))) self.proc = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) for line in self.proc.stdout: self.logger.debug('{}: {}'.format(self.vm['name'], line)) self.proc.wait() self.logger.info('Destroying {}'.format(self.vm['name'])) Popen(['bhyvectl', '--destroy', '--vm={}'.format(self.vm['name'])], stdout=subprocess.PIPE, stderr=subprocess.PIPE).wait() while self.taps: netif.destroy_interface(self.taps.pop()) self.manager._vm.pop(self.vm['id'], None)