def virtual_machine_exists(virtual_machine): """ Returns True/False depending if it exists or not. """ try: hedron.virtual_machine_info(virtual_machine) except Exception: return False return True
def network_argument(vm): """ Returns the network arguments that qemu will run with, if any. """ vm_info = hedron.virtual_machine_info(vm) if vm_info['ipv4'] is False and vm_info['ipv6'] is False: return [] hostaccessargument = '' if vm_info['hostaccess'] is True: hostaccessargument = ',guestfwd=tcp:10.0.2.1:1-tcp:127.0.0.1:22' if vm_info['ipv4'] in ['nat', 'tor'] and vm_info['ipv6'] in ['nat', 'tor']: sshport = vm_info['slot'] network_return = ['-net', 'nic,model=virtio', '-net'] args = 'user,hostfwd=tcp:127.0.0.1:{}-:22{}' network_return.append(args.format(sshport, hostaccessargument)) elif vm_info['ipv4'] == '/32' or vm_info['ipv6'] == '/128': mac = vm_info['network_interfaces'][0]['mac'] interface_name = 'slot{}'.format(vm_info['slot']) network_return = [ '-device', 'virtio-net-pci,netdev=' 'primary,mac={}'.format(mac), '-netdev', 'tap,id=primary,br=primary,' 'ifname={}'.format(interface_name) ] else: raise ValueError('Unsupported network argument combination.') return network_return
def qemu_arguments(vm): vm_info = hedron.virtual_machine_info(vm) arguments = [] arguments.append('-name') arguments.append(vm) arguments.append('-smp') arguments.append(str(vm_info['cores'])) arguments.append('-m') arguments.append('{}G'.format(vm_info['memory'])) arguments.append('-cpu') arguments.append('kvm64') arguments.append('-enable-kvm') arguments.append('-nodefaults') arguments.append('-monitor') arguments.append('none') arguments.append('-qmp') arguments.append('unix:/run/runqemu@{}/qmp,server,nowait'.format(vm)) arguments.extend(qemuopts_argument(vm)) drive = drive_argument(vm) if drive is not None: arguments.append('-drive') arguments.append(drive) arguments.append('-serial') arguments.append(serial_argument(vm)) arguments.extend(network_argument(vm)) arguments.extend(ipxe_iso_argument(vm)) arguments.extend(boot_order_argument(vm)) return arguments
def drive_argument(vm): vm_info = hedron.virtual_machine_info(vm) qcow2_path = '/var/tmp/runqemu/{}/disk.qcow2'.format(vm) drive_argument = DRIVE.format(qcow2_path) if vm_info['disk'] != 0: return drive_argument else: return None
def ndp_proxy(method, vm): validate_method(method) vm_info = hedron.virtual_machine_info(vm) ip6 = vm_info['network_interfaces'][0]['ipv6'] device = get_default_v6_route_device() sh.ip('-6', 'neigh', method, 'proxy', ip6, 'dev', device) return True
def is_slot_available(slot): """ Determines if a slot is available. """ for vm in hedron.list_virtual_machines(): if hedron.virtual_machine_info(vm)['slot'] == slot: return False return True
def list_ipv4s_in_use(): in_use = [] for vm in hedron.list_virtual_machines(): vm_info = hedron.virtual_machine_info(vm) if 'network_interfaces' in vm_info: if 'ipv4' in vm_info['network_interfaces'][0]: in_use.append(vm_info['network_interfaces'][0]['ipv4']) return in_use
def qemuopts_argument(vm): """ qemuopts is a string, not a list of options. """ vm_info = hedron.virtual_machine_info(vm) if vm_info['qemuopts'] is not None: return vm_info['qemuopts'].split() else: return DEFAULT_QEMUOPTS.split()
def dhcpd_config(): head = dhcpd_head() body = head for vm in hedron.list_virtual_machines(): vm_info = hedron.virtual_machine_info(vm) if 'network_interfaces' in vm_info: if 'ipv4' in vm_info['network_interfaces'][0]: mac = vm_info['network_interfaces'][0]['mac'] ipv4 = vm_info['network_interfaces'][0]['ipv4'] body = body + '\n' + dhcpd_snippet(vm, mac, ipv4) return body
def get_gid(vm): """ Logic filtering possible ipv4 and ipv6 options happens above this point. """ vm_info = hedron.virtual_machine_info(vm) # If we are using tor, map to the appropriate tornet process. if vm_info['ipv4'] == 'tor' or vm_info['ipv6'] == 'tor': return vm_info['slot'] else: # 1194 is "openvpn" which is not ran through tor. return 1194
def route(method, vm): """ Not needed if using primary is bridged with the public interface, but doesn't hurt, either. """ validate_method(method) vm_info = hedron.virtual_machine_info(vm) ip6 = vm_info['network_interfaces'][0]['ipv6'] arguments = _route_command(method, ip6) sh.ip(arguments) return True
def topup_vm_and_wait(topup_data): topup_vm(topup_data) machine_id = topup_data['machine_id'] expiration = topup_data['expiration'] # Up to 30 seconds for expiration to be updated. for tries in range(1, 30 + 1): data = hedron.virtual_machine_info(machine_id) if data['expiration'] == expiration: return True else: time.sleep(1) logging.critical('VM topup failed for {}'.format(machine_id)) raise ValueError('Fatal error, VM topup failed.')
def ebtables_arguments(vm, up=True): """ Returns the network arguments that qemu will run with, if any. For now, this primarily just filters mac address on output and ethertypes on both. """ vm_info = hedron.virtual_machine_info(vm) mac = vm_info['network_interfaces'][0]['mac'] interface = 'slot{}'.format(vm_info['slot']) # Insert if we are bringing the interface up, delete if bringing it down. if up is True: mode = '-A' else: mode = '-D' # FIXME: Too much redundancy # First is IPv4 and IPv6, second is IPv6 only. # --concurrent helps keep things stable. Can get weird rule ordering, # maybe duplicates without. if vm_info['ipv4'] == '/32': ebtables = ((mode, 'FORWARD', '-i', interface, '-s', mac, '-p', 'IPv4', '-j', 'ACCEPT', '--concurrent'), (mode, 'FORWARD', '-i', interface, '-s', mac, '-p', 'IPv6', '-j', 'ACCEPT', '--concurrent'), (mode, 'FORWARD', '-i', interface, '-s', mac, '-p', 'ARP', '-j', 'ACCEPT', '--concurrent'), (mode, 'FORWARD', '-i', interface, '-j', 'DROP', '--concurrent'), (mode, 'OUTPUT', '-o', interface, '-p', 'IPv4', '-j', 'ACCEPT', '--concurrent'), (mode, 'OUTPUT', '-o', interface, '-p', 'IPv6', '-j', 'ACCEPT', '--concurrent'), (mode, 'OUTPUT', '-o', interface, '-p', 'ARP', '-j', 'ACCEPT', '--concurrent'), (mode, 'OUTPUT', '-o', interface, '-j', 'DROP', '--concurrent')) else: ebtables = ((mode, 'FORWARD', '-i', interface, '-s', mac, '-p', 'IPv6', '-j', 'ACCEPT', '--concurrent'), (mode, 'FORWARD', '-i', interface, '-j', 'DROP', '--concurrent'), (mode, 'OUTPUT', '-o', interface, '-p', 'IPv6', '-j', 'ACCEPT', '--concurrent'), (mode, 'OUTPUT', '-o', interface, '-j', 'DROP', '--concurrent')) return ebtables
def list_expired_virtual_machines(): """ Lists all expired VMs on the system. """ now = time.time() expired_vms = [] for vm in hedron.list_virtual_machines(): vm_info = hedron.virtual_machine_info(vm) if vm_info['expiration'] != 0: # 0 means they never expire. if vm_info['expiration'] < now: time_delta = int(now - vm_info['expiration']) logging.debug('{} expired for {} seconds.'.format(vm, time_delta)) expired_vms.append(vm) else: time_delta = int(vm_info['expiration'] - now) logging.debug('{} expiring in {} seconds.'.format(vm, time_delta)) return expired_vms
def slot_to_vm(slot): """ Resolves a "slot40XX" to a VM ID. """ if len(slot) != 8: raise ValueError('slot argument must be 8 characters.') slot = int(slot[4:]) our_vm = None for vm in hedron.list_virtual_machines(): vm_info = hedron.virtual_machine_info(vm) if vm_info['slot'] == slot: our_vm = vm break if our_vm is None: raise ValueError('VM does not exist for that slot.') else: return our_vm
def get_used_resources(): """ Returns all potentially allocated VM resource counts. """ cores = 0 memory = 0 disk = 0 ipv4_addresses = 0 for vm in hedron.list_virtual_machines(): vm_info = hedron.virtual_machine_info(vm) cores = cores + vm_info['cores'] memory = memory + vm_info['memory'] disk = disk + vm_info['disk'] if vm_info['ipv4'] == '/32': ipv4_addresses = ipv4_addresses + 1 return { 'cores': cores, 'memory': memory, 'disk': disk, 'ipv4_addresses': ipv4_addresses }
def existing_txids(currency): """ Returns a list of txids already used for payment. This helps prevent a form of double spends. """ txids = [] if currency == 'settlement': return txids for vm in hedron.list_virtual_machines(): vm_info = hedron.virtual_machine_info(vm) if 'txid' not in vm_info: continue # Legacy support. 2019-08-05 if isinstance(vm_info['txid'], str): txids.append(vm_info['txid']) elif isinstance(vm_info['txid'], list): for txid in vm_info['txid']: txids.append(txid) return txids
def topup(options): """ Topup a VM. """ validate_options(options) machine_id = options['machine_id'] expiration = options['expiration'] currency = options['currency'] # Here we take a single txid as string. txid = options['txid'] vm_data = hedron.virtual_machine_info(options['machine_id']) logging.info('Topping up: {}'.format(options['machine_id'])) vm_data['expiration'] = expiration vm_data['currency'] = currency # Legacy support as of 2019-08-05 # Not likely one to be removed in the next year. if isinstance(vm_data['txid'], str): first_txid = vm_data['txid'] vm_data['txid'] = [first_txid, txid] elif isinstance(vm_data['txid'], list): vm_data['txid'].append(txid) else: vm_data['txid'] = [txid] directory = '/var/tmp/runqemu/{}'.format(machine_id) json_file = os.path.join(directory, 'settings.json') topup_json_file = json_file + '.topup' with open(topup_json_file, 'x') as json_file_fp: json.dump(vm_data, json_file_fp) # Atomic update. shutil.move(topup_json_file, json_file) return True
def boot_order_argument(vm): vm_info = hedron.virtual_machine_info(vm) bootorder = vm_info['bootorder'] # Hacky... we provide the "n" via the CD drive's iPXE iso bootorder = bootorder.replace('n', 'd') return ['-boot', 'order={}'.format(bootorder)]
def virtual_machine_topup(machine_id, days, currency, refund_address=None, override_code=None, settlement_token=None, affiliate_token=None, affiliate_amount=None): config = get_and_validate_config() # We should have a draining for topups separately. # if config['draining'] is True: # raise ValueError('Host is draining, unavailable for topups') if config['topup_enabled'] is False: raise ValueError('Host does not allow topups') return_data = {'latest_api_version': 2, 'payment': {'address': None, 'amount': 0}, 'refund_tx': None, 'created': False, 'paid': False, 'warning': None, 'expiration': 1, 'txid': None} validate.machine_id(machine_id) validate.refund_address(refund_address) validate.currency(currency) validate.affiliate_amount(affiliate_amount) # settlement_token is validated in settlers. logging.info('topup request for {}'.format(machine_id)) vm_data = hedron.virtual_machine_info(machine_id) if not override(override_code): if currency not in config['currencies']: msg = 'currency must be one of: {}'.format(config['currencies']) raise ValueError(msg) else: return_data['paid'] = True validate.days(days) if return_data['paid'] is False: address = config['currencies'][currency] # There is a bug with this, it uses the whole amount of bandwidth # over whatever timespan as the per-day calculation. So a 28 day server # at 32GiB per day will try to "pop up" at 28*32GiB per day and not # 32GiB per day. # bandwidth = vm_data['bandwidth'] # Hack for now. bandwidth = 0 cents = cost_in_cents(days=days, cores=vm_data['cores'], memory=vm_data['memory'], disk=vm_data['disk'], ipv4=vm_data['ipv4'], ipv6=vm_data['ipv6'], bandwidth=bandwidth) token = settlement_token business_token = config['settlers_business_token'] pay = payment(machine_id, currency, cents, address, existing_txids=existing_txids(currency), settlers_endpoint=config['settlers_endpoint'], settlers_customer_token=token, settlers_business_token=business_token, monero_rpc=config['monero_rpc'], affiliate_amount=affiliate_amount, affiliate_token=affiliate_token) return_data['txid'] = pay.txid return_data['payment']['amount'] = pay.amount return_data['payment']['uri'] = pay.uri return_data['payment']['usd'] = pay.usd return_data['payment']['address'] = pay.address if return_data['txid'] is not None: return_data['paid'] = True expiration = days_to_expiration(days=days, current_expiration=vm_data['expiration']) topup_data = {'machine_id': machine_id, 'expiration': expiration, 'currency': currency, 'txid': return_data['txid']} return_data['expiration'] = expiration if return_data['paid'] is True: topup_vm_and_wait(topup_data) return_data['toppedup'] = True # toppedup and paid should always be the same, True or False. return return_data