root / snf-cyclades-app / synnefo / admin / stats.py @ e780930a
History | View | Annotate | Download (9.5 kB)
1 |
# Copyright 2013 GRNET S.A. All rights reserved.
|
---|---|
2 |
#
|
3 |
# Redistribution and use in source and binary forms, with or
|
4 |
# without modification, are permitted provided that the following
|
5 |
# conditions are met:
|
6 |
#
|
7 |
# 1. Redistributions of source code must retain the above
|
8 |
# copyright notice, this list of conditions and the following
|
9 |
# disclaimer.
|
10 |
#
|
11 |
# 2. Redistributions in binary form must reproduce the above
|
12 |
# copyright notice, this list of conditions and the following
|
13 |
# disclaimer in the documentation and/or other materials
|
14 |
# provided with the distribution.
|
15 |
#
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
# POSSIBILITY OF SUCH DAMAGE.
|
28 |
#
|
29 |
# The views and conclusions contained in the software and
|
30 |
# documentation are those of the authors and should not be
|
31 |
# interpreted as representing official policies, either expressed
|
32 |
# or implied, of GRNET S.A.
|
33 |
|
34 |
|
35 |
import itertools |
36 |
import operator |
37 |
import datetime |
38 |
|
39 |
from collections import defaultdict # , OrderedDict |
40 |
from copy import copy |
41 |
from django.conf import settings |
42 |
from django.db.models import Count, Sum |
43 |
|
44 |
from snf_django.lib.astakos import UserCache |
45 |
from synnefo.db.models import VirtualMachine, Network, Backend |
46 |
from synnefo.plankton.utils import image_backend |
47 |
from synnefo.logic import backend as backend_mod |
48 |
|
49 |
|
50 |
def get_cyclades_stats(backend=None, clusters=True, servers=True, |
51 |
resources=True, networks=True, images=True): |
52 |
stats = {"datetime": datetime.datetime.now().strftime("%c")} |
53 |
if clusters:
|
54 |
stats["clusters"] = get_cluster_stats(backend=backend)
|
55 |
if servers:
|
56 |
stats["servers"] = get_servers_stats(backend=backend)
|
57 |
if resources:
|
58 |
stats["resources"] = get_resources_stats(backend=backend)
|
59 |
if networks:
|
60 |
stats["networks"] = get_networks_stats()
|
61 |
if images:
|
62 |
stats["images"] = get_images_stats(backend=None) |
63 |
return stats
|
64 |
|
65 |
|
66 |
def get_cluster_stats(backend): |
67 |
total = Backend.objects.all() |
68 |
stats = {"total": total.count(),
|
69 |
"drained": total.filter(drained=True).count(), |
70 |
"offline": total.filter(offline=True).count()} |
71 |
return stats
|
72 |
|
73 |
|
74 |
def _get_total_servers(backend=None): |
75 |
total_servers = VirtualMachine.objects.all() |
76 |
if backend is not None: |
77 |
total_servers = total_servers.filter(backend=backend) |
78 |
return total_servers
|
79 |
|
80 |
|
81 |
def get_servers_stats(backend=None): |
82 |
total_servers = _get_total_servers(backend=backend) |
83 |
per_state = total_servers.values("operstate")\
|
84 |
.annotate(count=Count("operstate"))
|
85 |
stats = {"total": 0} |
86 |
[stats.setdefault(s[0], 0) for s in VirtualMachine.OPER_STATES] |
87 |
for x in per_state: |
88 |
stats[x["operstate"]] = x["count"] |
89 |
stats["total"] += x["count"] |
90 |
return stats
|
91 |
|
92 |
|
93 |
def get_resources_stats(backend=None): |
94 |
total_servers = _get_total_servers(backend=backend) |
95 |
active_servers = total_servers.filter(deleted=False)
|
96 |
|
97 |
allocated = {} |
98 |
server_count = {} |
99 |
for res in ["cpu", "ram", "disk", "disk_template"]: |
100 |
server_count[res] = {} |
101 |
allocated[res] = 0
|
102 |
val = "flavor__%s" % res
|
103 |
results = active_servers.values(val).annotate(count=Count(val)) |
104 |
for result in results: |
105 |
server_count[res][result[val]] = result["count"]
|
106 |
if res != "disk_template": |
107 |
allocated[res] += result["count"]
|
108 |
|
109 |
resources_stats = get_backend_stats(backend=backend) |
110 |
for res in ["cpu", "ram", "disk", "disk_template"]: |
111 |
if res not in resources_stats: |
112 |
resources_stats[res] = {} |
113 |
resources_stats[res]["servers"] = server_count[res]
|
114 |
resources_stats[res]["allocated"] = allocated[res]
|
115 |
|
116 |
return resources_stats
|
117 |
|
118 |
|
119 |
def get_images_stats(backend=None): |
120 |
total_servers = _get_total_servers(backend=backend) |
121 |
active_servers = total_servers.filter(deleted=False)
|
122 |
|
123 |
active_servers_images = active_servers.values("imageid", "userid")\ |
124 |
.annotate(number=Count("imageid"))
|
125 |
|
126 |
image_cache = ImageCache() |
127 |
image_stats = defaultdict(int)
|
128 |
for result in active_servers_images: |
129 |
imageid = image_cache.get_image(result["imageid"], result["userid"]) |
130 |
image_stats[imageid] += result["number"]
|
131 |
return dict(image_stats) |
132 |
|
133 |
|
134 |
def get_networks_stats(): |
135 |
total_networks = Network.objects.all() |
136 |
stats = {"public_ips": get_ip_stats(),
|
137 |
"total": 0} |
138 |
per_state = total_networks.values("state")\
|
139 |
.annotate(count=Count("state"))
|
140 |
[stats.setdefault(s[0], 0) for s in Network.OPER_STATES] |
141 |
for x in per_state: |
142 |
stats[x["state"]] = x["count"] |
143 |
stats["total"] += x["count"] |
144 |
return stats
|
145 |
|
146 |
|
147 |
def group_by_resource(objects, resource): |
148 |
stats = {} |
149 |
key = operator.attrgetter("flavor."+resource)
|
150 |
grouped = itertools.groupby(sorted(objects, key=key), key)
|
151 |
for val, group in grouped: |
152 |
stats[val] = len(list(group)) |
153 |
return stats
|
154 |
|
155 |
|
156 |
def get_ip_stats(): |
157 |
total, free = 0, 0, |
158 |
for network in Network.objects.filter(public=True, deleted=False): |
159 |
try:
|
160 |
net_total, net_free = network.ip_count() |
161 |
except AttributeError: |
162 |
# TODO: Check that this works..
|
163 |
pool = network.get_pool(locked=False)
|
164 |
net_total = pool.pool_size |
165 |
net_free = pool.count_available() |
166 |
if not network.drained: |
167 |
total += net_total |
168 |
free += net_free |
169 |
return {"total": total, |
170 |
"free": free}
|
171 |
|
172 |
|
173 |
def get_backend_stats(backend=None): |
174 |
if backend is None: |
175 |
backends = Backend.objects.filter(offline=False)
|
176 |
else:
|
177 |
if backend.offline:
|
178 |
return {}
|
179 |
backends = [backend] |
180 |
[backend_mod.update_backend_resources(b) for b in backends] |
181 |
resources = {} |
182 |
for attr in ("dfree", "dtotal", "mfree", "mtotal", "ctotal"): |
183 |
resources[attr] = 0
|
184 |
for b in backends: |
185 |
resources[attr] += getattr(b, attr)
|
186 |
|
187 |
return {"disk": {"free": resources["dfree"], "total": resources["dtotal"]}, |
188 |
"ram": {"free": resources["mfree"], "total": resources["mtotal"]}, |
189 |
"cpu": {"free": resources["ctotal"], "total": resources["ctotal"]}, |
190 |
"disk_template": {"free": 0, "total": 0}} |
191 |
|
192 |
|
193 |
class ImageCache(object): |
194 |
def __init__(self): |
195 |
self.images = {}
|
196 |
usercache = UserCache(settings.ASTAKOS_BASE_URL, |
197 |
settings.CYCLADES_SERVICE_TOKEN) |
198 |
self.system_user_uuid = \
|
199 |
usercache.get_uuid(settings.SYSTEM_IMAGES_OWNER) |
200 |
|
201 |
def get_image(self, imageid, userid): |
202 |
if not imageid in self.images: |
203 |
try:
|
204 |
with image_backend(userid) as ib: |
205 |
image = ib.get_image(imageid) |
206 |
properties = image.get("properties")
|
207 |
os = properties.get("os",
|
208 |
properties.get("osfamily", "unknown")) |
209 |
owner = image["owner"]
|
210 |
owner = "system" if image["owner"] == self.system_user_uuid\ |
211 |
else "user" |
212 |
self.images[imageid] = owner + ":" + os |
213 |
except Exception: |
214 |
self.images[imageid] = "unknown:unknown" |
215 |
|
216 |
return self.images[imageid] |
217 |
|
218 |
|
219 |
def get_public_stats(): |
220 |
# VirtualMachines
|
221 |
vm_objects = VirtualMachine.objects |
222 |
servers = vm_objects.values("deleted", "operstate")\ |
223 |
.annotate(count=Count("id"),
|
224 |
cpu=Sum("flavor__cpu"),
|
225 |
ram=Sum("flavor__ram"),
|
226 |
disk=Sum("flavor__disk"))
|
227 |
zero_stats = {"count": 0, "cpu": 0, "ram": 0, "disk": 0} |
228 |
server_stats = {} |
229 |
for state in VirtualMachine.RSAPI_STATE_FROM_OPER_STATE.values(): |
230 |
server_stats[state] = copy(zero_stats) |
231 |
|
232 |
for stats in servers: |
233 |
deleted = stats.pop("deleted")
|
234 |
operstate = stats.pop("operstate")
|
235 |
state = VirtualMachine.RSAPI_STATE_FROM_OPER_STATE.get(operstate) |
236 |
if deleted:
|
237 |
for key in zero_stats.keys(): |
238 |
server_stats["DELETED"][key] += stats.get(key, 0) |
239 |
elif state:
|
240 |
for key in zero_stats.keys(): |
241 |
server_stats[state][key] += stats.get(key, 0)
|
242 |
|
243 |
#Networks
|
244 |
net_objects = Network.objects |
245 |
networks = net_objects.values("deleted", "state")\ |
246 |
.annotate(count=Count("id"))
|
247 |
zero_stats = {"count": 0} |
248 |
network_stats = {} |
249 |
for state in Network.RSAPI_STATE_FROM_OPER_STATE.values(): |
250 |
network_stats[state] = copy(zero_stats) |
251 |
|
252 |
for stats in networks: |
253 |
deleted = stats.pop("deleted")
|
254 |
state = stats.pop("state")
|
255 |
state = Network.RSAPI_STATE_FROM_OPER_STATE.get(state) |
256 |
if deleted:
|
257 |
for key in zero_stats.keys(): |
258 |
network_stats["DELETED"][key] += stats.get(key, 0) |
259 |
elif state:
|
260 |
for key in zero_stats.keys(): |
261 |
network_stats[state][key] += stats.get(key, 0)
|
262 |
|
263 |
statistics = {"servers": server_stats,
|
264 |
"networks": network_stats}
|
265 |
return statistics
|