+65
-44
capsulflask/admin.py
+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
+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
+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
+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
+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 %}