capsul.org webapp

trying to get admin tools in place

forest 2ce638ab fd0d1128

Changed files
+189 -100
capsulflask
+65 -44
capsulflask/admin.py
··· 11 11 from capsulflask.metrics import durations as metric_durations 12 12 from capsulflask.auth import admin_account_required 13 13 from capsulflask.db import get_model 14 + from capsulflask.consistency import get_all_vms_from_db, get_all_vms_from_hosts 14 15 from capsulflask.shared import my_exec_info_message 15 16 16 17 bp = Blueprint("admin", __name__, url_prefix="/admin") ··· 48 49 else: 49 50 return abort(400, "unknown form action") 50 51 52 + # moving on from the form post stuff... 51 53 52 54 # first create the hosts list w/ ip allocation visualization from the database 53 55 # 54 56 55 57 db_hosts = get_model().list_hosts_with_networks(None) 56 - db_vms_by_host_and_network = get_model().non_deleted_vms_by_host_and_network(None) 58 + db_vms_by_id = get_all_vms_from_db() 57 59 network_display_width_px = float(270) 58 60 #operations = get_model().list_all_operations() 59 61 ··· 64 66 {'}'} 65 67 """] 66 68 67 - db_vm_by_id = dict() 69 + db_vms_by_host_network = dict() 70 + for vm in db_vms_by_id.values(): 71 + host_network_key = f"{vm['host']}_{vm['network_name']}" 72 + if host_network_key not in db_vms_by_host_network: 73 + db_vms_by_host_network[host_network_key] = [] 74 + db_vms_by_host_network[host_network_key].append(vm) 75 + 68 76 69 77 for kv in db_hosts.items(): 70 78 host_id = kv[0] ··· 79 87 network['allocations'] = [] 80 88 network_addresses_width = float((network_end_int-network_start_int)+1) 81 89 82 - if host_id in db_vms_by_host_and_network: 83 - if network['network_name'] in db_vms_by_host_and_network[host_id]: 84 - for vm in db_vms_by_host_and_network[host_id][network['network_name']]: 85 - vm['network_name'] = network['network_name'] 86 - vm['virtual_bridge_name'] = network['virtual_bridge_name'] 87 - vm['host'] = host_id 88 - db_vm_by_id[vm['id']] = vm 89 - ip_address_int = int(ipaddress.ip_address(vm['public_ipv4'])) 90 - if network_start_int <= ip_address_int and ip_address_int <= network_end_int: 91 - allocation = f"{host_id}_{network['network_name']}_{len(network['allocations'])}" 92 - inline_styles.append( 93 - f""" 94 - .{allocation} {'{'} 95 - left: {(float(ip_address_int-network_start_int)/network_addresses_width)*network_display_width_px}px; 96 - width: {network_display_width_px/network_addresses_width}px; 97 - {'}'} 98 - """ 99 - ) 100 - network['allocations'].append(allocation) 101 - else: 102 - current_app.logger.warning(f"/admin: capsul {vm['id']} has public_ipv4 {vm['public_ipv4']} which is out of range for its host network {host_id} {network['network_name']} {network['public_ipv4_cidr_block']}") 103 - 90 + host_network_key = f"{host_id}_{network['network_name']}" 91 + if host_network_key in db_vms_by_host_network: 92 + for vm in db_vms_by_host_network[host_network_key]: 93 + ip_address_int = int(ipaddress.ip_address(vm['public_ipv4'])) 94 + if network_start_int <= ip_address_int and ip_address_int <= network_end_int: 95 + allocation = f"{host_id}_{network['network_name']}_{len(network['allocations'])}" 96 + inline_styles.append( 97 + f""" 98 + .{allocation} {'{'} 99 + left: {(float(ip_address_int-network_start_int)/network_addresses_width)*network_display_width_px}px; 100 + width: {network_display_width_px/network_addresses_width}px; 101 + {'}'} 102 + """ 103 + ) 104 + network['allocations'].append(allocation) 105 + else: 106 + current_app.logger.warning(f"/admin: capsul {vm['id']} has public_ipv4 {vm['public_ipv4']} which is out of range for its host network {host_id} {network['network_name']} {network['public_ipv4_cidr_block']}") 107 + 104 108 display_hosts.append(display_host) 105 109 106 110 107 - # Now creating the capsuls running status ui 111 + # Now creating the capsul consistency / running status ui 108 112 # 109 113 110 - virt_vms_by_host_and_network = current_app.config["HUB_MODEL"].get_all_by_host_and_network() 111 - 112 - # virt_vms_dict = dict() 113 - # for vm in virt_vms: 114 - # virt_vms_dict[vm["id"]] = vm["state"] 114 + virt_vms_by_id = get_all_vms_from_hosts() 115 115 116 - # in_db_but_not_in_virt = [] 117 - # needs_to_be_started = [] 118 - # needs_to_be_started_missing_ipv4 = [] 116 + virt_vm_id_by_ipv4 = dict() 117 + for vm_id, virt_vm in virt_vms_by_id.items(): 118 + virt_vm_id_by_ipv4[virt_vm['public_ipv4']] = vm_id 119 119 120 - # for vm in db_vms: 121 - # if vm["id"] not in virt_vms_dict: 122 - # in_db_but_not_in_virt.append(vm["id"]) 123 - # elif vm["desired_state"] == "running" and virt_vms_dict[vm["id"]] != "running": 124 - # if vm["id"] in db_vm_by_id: 125 - # needs_to_be_started.append(db_vm_by_id[vm["id"]]) 126 - # else: 127 - # needs_to_be_started_missing_ipv4.append(vm["id"]) 128 - # elif vm["ipv4"] != current_ipv4 120 + db_vm_id_by_ipv4 = dict() 121 + for vm_id, db_vm in db_vms_by_id.items(): 122 + db_vm_id_by_ipv4[db_vm['public_ipv4']] = vm_id 123 + 124 + in_db_but_not_in_virt = [] 125 + state_not_equal_to_desired_state = [] 126 + stole_someone_elses_ip_and_own_ip_avaliable = [] 127 + stole_someone_elses_ip_but_own_ip_also_stolen = [] 128 + has_wrong_ip = [] 129 + 130 + for vm_id, db_vm in db_vms_by_id.items(): 131 + if vm_id not in virt_vms_by_id: 132 + in_db_but_not_in_virt.append(db_vm) 133 + elif virt_vms_by_id[vm_id]['state'] != db_vm["desired_state"]: 134 + db_vm["state"] = virt_vms_by_id[vm_id]['state'] 135 + state_not_equal_to_desired_state.append(db_vm) 136 + elif virt_vms_by_id[vm_id]['public_ipv4'] != db_vm["public_ipv4"]: 137 + db_vm["desired_ipv4"] = db_vm["public_ipv4"] 138 + db_vm["current_ipv4"] = virt_vms_by_id[vm_id]['public_ipv4'] 139 + if virt_vms_by_id[vm_id]['public_ipv4'] in db_vm_id_by_ipv4: 140 + if db_vm["public_ipv4"] not in virt_vm_id_by_ipv4: 141 + stole_someone_elses_ip_and_own_ip_avaliable.append(db_vm) 142 + else: 143 + stole_someone_elses_ip_but_own_ip_also_stolen.append(db_vm) 144 + 145 + has_wrong_ip.append(db_vm) 146 + 129 147 130 148 # current_app.logger.info(f"list_of_networks: {json.dumps(list_of_networks)}") 131 149 ··· 142 160 csp_inline_style_nonce=csp_inline_style_nonce, 143 161 inline_style='\n'.join(inline_styles), 144 162 145 - db_vms_by_host_and_network=json.dumps(db_vms_by_host_and_network), 146 - virt_vms_by_host_and_network=json.dumps(virt_vms_by_host_and_network), 163 + in_db_but_not_in_virt=in_db_but_not_in_virt, 164 + state_not_equal_to_desired_state=state_not_equal_to_desired_state, 165 + stole_someone_elses_ip_and_own_ip_avaliable=stole_someone_elses_ip_and_own_ip_avaliable, 166 + stole_someone_elses_ip_but_own_ip_also_stolen=stole_someone_elses_ip_but_own_ip_also_stolen, 167 + has_wrong_ip=has_wrong_ip 147 168 ) 148 169 149 170 response = make_response(response_text)
+41 -41
capsulflask/consistency.py
··· 10 10 # "host": "baikal", 11 11 # "network_name": "public1", 12 12 # "virtual_bridge_name": "virbr1", 13 - # "state": "running" 13 + # "desired_state": "running" 14 14 # }, 15 15 # { ... }, 16 16 # ... ··· 41 41 42 42 return db_vms_by_id 43 43 44 + 45 + # this returns the same shape of object as get_all_vms_from_db except it has 'state' instead of 'desired_state' 44 46 def get_all_vms_from_hosts() -> dict: 45 - virt_vms = current_app.config["HUB_MODEL"].get_all_by_host_and_network() 47 + virt_vms_by_host_and_network = current_app.config["HUB_MODEL"].get_all_by_host_and_network() 46 48 #virt_networks = current_app.config["HUB_MODEL"].virsh_netlist() 47 49 db_hosts = get_model().list_hosts_with_networks(None) 48 50 ··· 52 54 host_id = kv[0] 53 55 value = kv[1] 54 56 for network in value['networks']: 55 - 57 + if host_id in virt_vms_by_host_and_network and network['network_name'] in virt_vms_by_host_and_network[host_id]: 58 + for vm in virt_vms_by_host_and_network[host_id][network['network_name']]: 59 + vm['network_name'] = network['network_name'] 60 + vm['virtual_bridge_name'] = network['virtual_bridge_name'] 61 + vm['host'] = host_id 62 + virt_vms_by_id[vm['id']] = vm 56 63 57 - for vm in db_vms: 58 - if vm["id"] not in db_vms_by_id: 59 - # TODO 60 - raise Exception("non_deleted_vms_by_host_and_network did not return a vm that was returned by all_vm_ids_with_desired_state") 61 - else: 62 - db_vms_by_id[vm["id"]]["state"] = vm["desired_state"] 63 - 64 - virt_vms = current_app.config["HUB_MODEL"].get_vm_() 64 + return virt_vms_by_id 65 65 66 66 def ensure_vms_and_db_are_synced(): 67 67 68 68 69 - 70 - # Now creating the capsuls running status ui 71 - # 69 + pass 70 + # # Now creating the capsuls running status ui 71 + # # 72 72 73 73 74 74 75 - for vm in db_vms: 76 - db_ids_dict[vm['id']] = vm['desired_state'] 75 + # for vm in db_vms: 76 + # db_ids_dict[vm['id']] = vm['desired_state'] 77 77 78 - for vm in virt_vms: 79 - virt_ids_dict[vm['id']] = vm['desired_state'] 78 + # for vm in virt_vms: 79 + # virt_ids_dict[vm['id']] = vm['desired_state'] 80 80 81 - errors = list() 81 + # errors = list() 82 82 83 - for id in db_ids_dict: 84 - if id not in virt_ids_dict: 85 - errors.append(f"{id} is in the database but not in the virtualization model") 86 - elif db_ids_dict[id] != virt_ids_dict[id]: 87 - errors.append(f"{id} has the desired state {db_ids_dict[id]} in the database but current state {virt_ids_dict[id]} in the virtualization model") 83 + # for id in db_ids_dict: 84 + # if id not in virt_ids_dict: 85 + # errors.append(f"{id} is in the database but not in the virtualization model") 86 + # elif db_ids_dict[id] != virt_ids_dict[id]: 87 + # errors.append(f"{id} has the desired state {db_ids_dict[id]} in the database but current state {virt_ids_dict[id]} in the virtualization model") 88 88 89 - for id in virt_ids_dict: 90 - if id not in db_ids_dict: 91 - errors.append(f"{id} is in the virtualization model but not in the database") 89 + # for id in virt_ids_dict: 90 + # if id not in db_ids_dict: 91 + # errors.append(f"{id} is in the virtualization model but not in the database") 92 92 93 - if len(errors) > 0: 94 - email_addresses_raw = current_app.config['ADMIN_EMAIL_ADDRESSES'].split(",") 95 - email_addresses = list(filter(lambda x: len(x) > 6, map(lambda x: x.strip(), email_addresses_raw ) )) 93 + # if len(errors) > 0: 94 + # email_addresses_raw = current_app.config['ADMIN_EMAIL_ADDRESSES'].split(",") 95 + # email_addresses = list(filter(lambda x: len(x) > 6, map(lambda x: x.strip(), email_addresses_raw ) )) 96 96 97 - current_app.logger.info(f"cron_task: sending inconsistency warning email to {','.join(email_addresses)}:") 98 - for error in errors: 99 - current_app.logger.info(f"cron_task: {error}.") 97 + # current_app.logger.info(f"cron_task: sending inconsistency warning email to {','.join(email_addresses)}:") 98 + # for error in errors: 99 + # current_app.logger.info(f"cron_task: {error}.") 100 100 101 - current_app.config["FLASK_MAIL_INSTANCE"].send( 102 - Message( 103 - "Capsul Consistency Check Failed", 104 - sender=current_app.config["MAIL_DEFAULT_SENDER"], 105 - body="\n".join(errors), 106 - recipients=email_addresses 107 - ) 108 - ) 101 + # current_app.config["FLASK_MAIL_INSTANCE"].send( 102 + # Message( 103 + # "Capsul Consistency Check Failed", 104 + # sender=current_app.config["MAIL_DEFAULT_SENDER"], 105 + # body="\n".join(errors), 106 + # recipients=email_addresses 107 + # ) 108 + # )
+1 -1
capsulflask/shell_scripts/virsh-net-list.sh
··· 7 7 if [ "$network_name" != "" ]; then 8 8 virtual_bridge_name="$(virsh net-info "$network_name" | grep -E '^Bridge:' | awk '{ print $2 }')" 9 9 capsul_state="$(echo "$line" | sed -E 's/^ *[0-9-]+ +[^ ]+ +//')" 10 - printf '%s\n {"name":"%s", "virtual_bridge_name":"%s"}' "$delimiter" "$network_name" "$virtual_bridge_name" 10 + printf '%s\n {"network_name":"%s", "virtual_bridge_name":"%s"}' "$delimiter" "$network_name" "$virtual_bridge_name" 11 11 delimiter="," 12 12 fi 13 13 done
+3 -5
capsulflask/spoke_model.py
··· 184 184 else: 185 185 current_app.logger.warn(f"get_all_by_host_and_network: '{vm['domain']}' not in vm_state_by_id, defaulting to 'shut off'") 186 186 187 - vms_by_id[vm['domain']] = dict(macs=dict(), state=vm_state, network=network['name']) 187 + vms_by_id[vm['domain']] = dict(macs=dict(), state=vm_state, network_name=network['network_name']) 188 188 189 189 vms_by_id[vm['domain']]['macs'][mac] = True 190 190 ··· 199 199 for status in statuses: 200 200 if status['mac-address'] in vm_id_by_mac: 201 201 vm_id = vm_id_by_mac[status['mac-address']] 202 - vms_by_id[vm_id]['ipv4'] = status['ip-address'] 202 + vms_by_id[vm_id]['public_ipv4'] = status['ip-address'] 203 203 else: 204 204 current_app.logger.warn(f"get_all_by_host_and_network: {status['mac-address']} not in vm_id_by_mac") 205 205 206 206 networks = dict() 207 - for vm_id in vms_by_id: 208 - vm = vms_by_id[vm_id] 209 - 207 + for vm in vms_by_id.values(): 210 208 if vm['network'] not in networks: 211 209 networks[vm['network']] = [] 212 210
+79 -9
capsulflask/templates/admin.html
··· 36 36 37 37 <hr/> 38 38 {% endfor %} 39 + </div> 39 40 41 + {% if in_db_but_not_in_virt|length > 0 %} 42 + <div class="third-margin"> 43 + <h1>๐Ÿšจ in the database but not in the virtualization model ๐Ÿšจ</h1> 44 + {% for vm in in_db_but_not_in_virt %} 40 45 <div class="row"> 41 - <h1>db_vms_by_host_and_network</h1> 46 + {{vm['id']}} {{vm['public_ipv4']}} 42 47 </div> 48 + {% endfor %} 49 + <hr/> 50 + </div> 51 + {% endif %} 52 + 53 + 54 + 55 + {% if state_not_equal_to_desired_state|length > 0 %} 56 + <div class="third-margin"> 57 + <h1>๐Ÿ˜ด vm state != desired state ๐Ÿ˜ด</h1> 58 + {% for vm in state_not_equal_to_desired_state %} 43 59 <div class="row"> 44 - <pre> 45 - {{db_vms_by_host_and_network}} 46 - </pre> 60 + <div>{{vm['id']}}: state={{vm['state']}} desired_state={{vm['desired_state']}}</div> 61 + <form method="post"> 62 + <input type="hidden" name="action" value="set_state"></input> 63 + <input type="hidden" name="id" value="{{vm['id']}}"></input> 64 + <input type="hidden" name="state" value="{{vm['desired_state']}}"></input> 65 + <input type="hidden" name="csrf-token" value="{{ csrf_token }}"/> 66 + <input type="submit" value="๐Ÿšฆ START/STOP"/> 67 + </form> 47 68 </div> 69 + {% endfor %} 70 + <hr/> 71 + </div> 72 + {% endif %} 48 73 74 + 75 + 76 + {% if stole_someone_elses_ip_and_own_ip_avaliable|length > 0 %} 77 + <div class="third-margin"> 78 + <h1>๐Ÿ‘ป stole someone elses ip and own desired ip is avaliable ๐Ÿ‘ป</h1> 79 + {% for vm in stole_someone_elses_ip_and_own_ip_avaliable %} 49 80 <div class="row"> 50 - <h1>virt_vms_by_host_and_network</h1> 81 + <div>{{vm['id']}}: current_ipv4={{vm['current_ipv4']}} desired_ipv4={{vm['desired_ipv4']}}</div> 82 + <form method="post"> 83 + <input type="hidden" name="action" value="dhcp_reset"></input> 84 + <input type="hidden" name="id" value="{{vm['id']}}"></input> 85 + <input type="hidden" name="csrf-token" value="{{ csrf_token }}"/> 86 + <input type="submit" value="๐Ÿ”จ DHCP RESET"/> 87 + </form> 51 88 </div> 89 + {% endfor %} 90 + <hr/> 91 + </div> 92 + {% endif %} 93 + 94 + {% if has_wrong_ip|length > 0 %} 95 + <div class="third-margin"> 96 + <h1>๐Ÿฅด has wrong ip address ๐Ÿฅด</h1> 97 + {% for vm in has_wrong_ip %} 52 98 <div class="row"> 53 - <pre> 54 - {{virt_vms_by_host_and_network}} 55 - </pre> 99 + <div>{{vm['id']}}: current_ipv4={{vm['current_ipv4']}} desired_ipv4={{vm['desired_ipv4']}}</div> 100 + <form method="post"> 101 + <input type="hidden" name="action" value="dhcp_reset"></input> 102 + <input type="hidden" name="id" value="{{vm['id']}}"></input> 103 + <input type="hidden" name="csrf-token" value="{{ csrf_token }}"/> 104 + <input type="submit" value="๐Ÿ”จ DHCP RESET"/> 105 + </form> 56 106 </div> 107 + {% endfor %} 108 + <hr/> 109 + </div> 110 + {% endif %} 57 111 58 112 59 - 113 + {% if stole_someone_elses_ip_but_own_ip_also_stolen|length > 0 %} 114 + <div class="third-margin"> 115 + <h1>๐Ÿ’€ stole someone elses ip but own desired ip was also stolen ๐Ÿ’€</h1> 116 + {% for vm in stole_someone_elses_ip_but_own_ip_also_stolen %} 117 + <div class="row"> 118 + <div>{{vm['id']}}: current_ipv4={{vm['current_ipv4']}} desired_ipv4={{vm['desired_ipv4']}}</div> 119 + <form method="post"> 120 + <input type="hidden" name="action" value="stop_and_expire"></input> 121 + <input type="hidden" name="id" value="{{vm['id']}}"></input> 122 + <input type="hidden" name="csrf-token" value="{{ csrf_token }}"/> 123 + <input type="submit" value="๐Ÿ›‘ STOP AND EXPIRE DHCP LEASE"/> 124 + </form> 125 + </div> 126 + {% endfor %} 127 + <hr/> 60 128 </div> 129 + {% endif %} 61 130 62 131 <div class="third-margin"> 63 132 <div class="row"> ··· 74 143 </div> 75 144 76 145 </div> 146 + 77 147 {% endblock %} 78 148 79 149 {% block pagesource %}/templates/admin.html{% endblock %}