3 # Copyright 2016 RIFT.IO Inc
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
19 import dateutil
.parser
27 gi
.require_version('RwTypes', '1.0')
28 gi
.require_version('RwcalYang', '1.0')
29 gi
.require_version('RwmonYang', '1.0')
31 from gi
.repository
import (
38 import rift
.rwcal
.openstack
as openstack_drv
42 logger
= logging
.getLogger('rwmon.ceilometer')
44 rwstatus
= rw_status
.rwstatus_from_exc_map({
45 IndexError: RwTypes
.RwStatus
.NOTFOUND
,
46 KeyError: RwTypes
.RwStatus
.NOTFOUND
,
50 class UnknownService(Exception):
54 class CeilometerMonitoringPlugin(GObject
.Object
, RwMon
.Monitoring
):
56 GObject
.Object
.__init
__(self
)
57 self
._driver
_class
= openstack_drv
.OpenstackDriver
59 def _get_driver(self
, account
):
60 return self
._driver
_class
(username
= account
.openstack
.key
,
61 password
= account
.openstack
.secret
,
62 auth_url
= account
.openstack
.auth_url
,
63 tenant_name
= account
.openstack
.tenant
,
64 mgmt_network
= account
.openstack
.mgmt_network
)
67 def do_init(self
, rwlog_ctx
):
68 if not any(isinstance(h
, rwlogger
.RwLogger
) for h
in logger
.handlers
):
71 category
="rw-monitor-log",
72 subcategory
="ceilometer",
77 @rwstatus(ret_on_failure
=[None])
78 def do_nfvi_metrics(self
, account
, vm_id
):
80 samples
= self
._get
_driver
(account
).ceilo_nfvi_metrics(vm_id
)
82 metrics
= RwmonYang
.NfviMetrics()
84 vcpu
= samples
.get("cpu_util", {})
85 memory
= samples
.get("memory_usage", {})
86 storage
= samples
.get("disk_usage", {})
88 metrics
.vcpu
.utilization
= vcpu
.get("volume", 0)
89 metrics
.memory
.used
= memory
.get("volume", 0)
90 metrics
.storage
.used
= storage
.get("volume", 0)
92 def convert_timestamp(t
):
93 return dateutil
.parser
.parse(t
).timestamp()
96 if 'timestamp' in vcpu
:
97 timestamps
.append(convert_timestamp(vcpu
['timestamp']))
98 if 'timestamp' in memory
:
99 timestamps
.append(convert_timestamp(memory
['timestamp']))
100 if 'timestamp' in storage
:
101 timestamps
.append(convert_timestamp(storage
['timestamp']))
103 metrics
.timestamp
= max(timestamps
) if timestamps
else 0.0
107 except Exception as e
:
110 @rwstatus(ret_on_failure
=[None])
111 def do_nfvi_vcpu_metrics(self
, account
, vm_id
):
113 samples
= self
._get
_driver
(account
).ceilo_nfvi_metrics(vm_id
)
115 metrics
= RwmonYang
.NfviMetrics_Vcpu()
116 metrics
.utilization
= samples
.get("cpu_util", 0)
120 except Exception as e
:
123 @rwstatus(ret_on_failure
=[None])
124 def do_nfvi_memory_metrics(self
, account
, vm_id
):
126 samples
= self
._get
_driver
(account
).ceilo_nfvi_metrics(vm_id
)
128 metrics
= RwmonYang
.NfviMetrics_Memory()
129 metrics
.used
= samples
.get("memory_usage", 0)
133 except Exception as e
:
136 @rwstatus(ret_on_failure
=[None])
137 def do_nfvi_storage_metrics(self
, account
, vm_id
):
139 samples
= self
._get
_driver
(account
).ceilo_nfvi_metrics(vm_id
)
141 metrics
= RwmonYang
.NfviMetrics_Storage()
142 metrics
.used
= samples
.get("disk_usage", 0)
146 except Exception as e
:
149 @rwstatus(ret_on_failure
=[False])
150 def do_nfvi_metrics_available(self
, account
):
152 endpoint
= self
._get
_driver
(account
).ceilo_meter_endpoint()
156 return endpoint
is not None
158 @rwstatus(ret_on_failure
=[None])
159 def do_alarm_create(self
, account
, vim_id
, alarm
):
160 # Retrieve a token using account information
161 token
= openstack_auth_token(account
)
162 service
= token
.service("ceilometer")
163 headers
= {"content-type": "application/json", "x-auth-token": token
.id}
165 # Convert the alarm from its YANG representation into something that
166 # can be passed to the openstack interface
167 ceilometer_alarm
= CeilometerAlarm
.from_gi_obj(alarm
, vim_id
).to_dict()
169 # POST the data to ceilometer
170 response
= requests
.post(
171 service
.url
.public
+ "/v2/alarms",
173 data
=json
.dumps(ceilometer_alarm
),
177 # Returns the response object and update the alarm ID
178 obj
= response
.json()
179 alarm
.alarm_id
= obj
['alarm_id']
182 @rwstatus(ret_on_failure
=[None])
183 def do_alarm_update(self
, account
, alarm
):
184 # Retrieve a token using account information
185 token
= openstack_auth_token(account
)
186 service
= token
.service("ceilometer")
187 headers
= {"content-type": "application/json", "x-auth-token": token
.id}
189 # Convert the alarm from its YANG representation into something that
190 # can be passed to the openstack interface
191 ceilometer_alarm
= CeilometerAlarm
.from_gi_obj(alarm
).to_dict()
193 # PUT the data to ceilometer
194 response
= requests
.put(
195 service
.url
.public
+ "/v2/alarms/{}".format(alarm
.alarm_id
),
197 data
=json
.dumps(ceilometer_alarm
),
201 return response
.json()
203 @rwstatus(ret_on_failure
=[None])
204 def do_alarm_delete(self
, account
, alarm_id
):
205 # Retrieve a token using account information
206 token
= openstack_auth_token(account
)
207 service
= token
.service("ceilometer")
208 headers
= {"content-type": "application/json", "x-auth-token": token
.id}
212 service
.url
.public
+ "/v2/alarms/{}".format(alarm_id
),
217 @rwstatus(ret_on_failure
=[None])
218 def do_alarm_list(self
, account
):
219 # Retrieve a token using account information
220 token
= openstack_auth_token(account
)
221 service
= token
.service("ceilometer")
222 headers
= {"x-auth-token": token
.id}
224 # GET a list of alarms
225 response
= requests
.get(
226 service
.url
.public
+ "/v2/alarms",
231 return response
.json()
234 class OpenstackAuthTokenV2(object):
235 def __init__(self
, data
):
239 def request(cls
, account
):
240 """Create an OpenstackAuthTokenV2 using account information
243 account - an RwcalYang.CloudAccount object
249 headers
= {"content-type": "application/json"}
252 "tenantName": account
.openstack
.tenant
,
253 "passwordCredentials": {
254 "username": account
.openstack
.key
,
255 "password": account
.openstack
.secret
,
260 url
= "{}/tokens".format(account
.openstack
.auth_url
)
261 response
= requests
.post(url
, headers
=headers
, data
=data
)
262 response
.raise_for_status()
264 return cls(response
.json())
268 """The token identifier"""
269 return self
._data
["access"]["token"]["id"]
271 def service(self
, name
):
272 """Returns information about the specified service
275 name - the name of the service to return
278 If the requested service cannot be found, an UnknownService
282 an OpenstackService object
285 for s
in self
._data
["access"]["serviceCatalog"]:
286 if s
["name"] == name
:
287 return OpenstackService(
289 url
=OpenstackServiceURLs(
290 public
=s
["endpoints"][0]["publicURL"],
291 internal
=s
["endpoints"][0]["internalURL"],
292 admin
=s
["endpoints"][0]["adminURL"],
296 raise UnknownService(name
)
299 class OpenstackAuthTokenV3(object):
300 def __init__(self
, token
, data
):
305 def request(cls
, account
):
306 """Create an OpenstackAuthTokenV3 using account information
309 account - an RwcalYang.CloudAccount object
315 headers
= {"content-type": "application/json"}
319 "methods": ["password"],
322 "name": account
.openstack
.key
,
323 "password": account
.openstack
.secret
,
324 "domain": {"id": "default"},
330 "name": account
.openstack
.tenant
,
331 "domain": {"id": "default"},
337 url
= account
.openstack
.auth_url
+ "/auth/tokens"
339 response
= requests
.post(url
, headers
=headers
, data
=data
)
340 response
.raise_for_status()
342 return cls(response
.headers
['x-subject-token'], response
.json())
346 """The token identifier"""
349 def service(self
, name
):
350 """Returns information about the specified service
353 name - the name of the service to return
356 If the requested service cannot be found, an UnknownService
360 an OpenstackService object
363 for s
in self
._data
["token"]["catalog"]:
364 if s
["name"] == name
:
365 endpoints
= {e
["interface"]:e
["url"] for e
in s
["endpoints"]}
366 return OpenstackService(
368 url
=OpenstackServiceURLs(
369 public
=endpoints
["public"],
370 internal
=endpoints
["internal"],
371 admin
=endpoints
["admin"],
375 raise UnknownService(name
)
378 def openstack_auth_token(account
):
379 url
= urllib
.parse
.urlparse(account
.openstack
.auth_url
)
381 if url
.path
in ('/v3',):
382 return OpenstackAuthTokenV3
.request(account
)
384 if url
.path
in ('/v2.0', 'v2.1'):
385 return OpenstackAuthTokenV2
.request(account
)
387 raise ValueError("Unrecognized keystone version")
390 class OpenstackService(collections
.namedtuple(
396 class OpenstackServiceURLs(collections
.namedtuple(
397 "OpenstackServiceURLs",
398 "public internal admin")):
402 class CeilometerAlarm(collections
.namedtuple(
404 "name type description severity repeat_actions enabled alarm_actions ok_actions insufficient_data_actions threshold_rule")):
406 def from_gi_obj(cls
, alarm
, vim_id
=None):
407 severity
= CeilometerAlarmSeverity
.from_gi_obj(alarm
.severity
).severity
408 actions
= CeilometerAlarmActions
.from_gi_obj(alarm
.actions
)
410 alarm_id
= alarm
.alarm_id
if vim_id
is None else vim_id
411 threshold_rule
= CeilometerThresholdRule
.from_gi_obj(alarm_id
, alarm
)
416 description
=alarm
.description
,
418 repeat_actions
=alarm
.repeat
,
419 enabled
=alarm
.enabled
,
420 threshold_rule
=threshold_rule
,
421 ok_actions
=actions
.ok
,
422 alarm_actions
=actions
.alarm
,
423 insufficient_data_actions
=actions
.insufficient_data
,
427 """Returns a dictionary containing the tuple data"""
428 def recursive_to_dict(obj
):
429 if not hasattr(obj
, '_fields'):
432 return {k
: recursive_to_dict(getattr(obj
, k
)) for k
in obj
._fields
}
434 return recursive_to_dict(self
)
437 class CeilometerThresholdRule(collections
.namedtuple(
438 "CeilometerThresholdRule",
439 "evaluation_periods threshold statistic meter_name comparison_operator period query")):
441 def from_gi_obj(cls
, vim_id
, alarm
):
442 meter
= CeilometerAlarmMeter
.from_gi_obj(alarm
.metric
).meter
443 statistic
= CeilometerAlarmStatistic
.from_gi_obj(alarm
.statistic
).statistic
444 operation
= CeilometerAlarmOperation
.from_gi_obj(alarm
.operation
).operation
447 evaluation_periods
=alarm
.evaluations
,
448 threshold
=alarm
.value
,
451 comparison_operator
=operation
,
455 "field": "resource_id",
461 """Returns a dictionary containing the tuple data"""
462 def recursive_to_dict(obj
):
463 if not hasattr(obj
, '_fields'):
466 return {k
: recursive_to_dict(getattr(obj
, k
)) for k
in obj
._fields
}
468 return recursive_to_dict(self
)
471 class CeilometerAlarmMeter(collections
.namedtuple(
472 "CeiloemterAlarmMeter",
475 "CPU_UTILIZATION" : "cpu_util",
476 "MEMORY_UTILIZATION" : "memory_usage",
477 "STORAGE_UTILIZATION" : "disk_usage",
480 def from_gi_obj(cls
, meter
):
481 return cls(meter
=cls
.__mapping
__[meter
])
484 class CeilometerAlarmStatistic(collections
.namedtuple(
485 "CeilometerAlarmStatistic",
495 def from_gi_obj(cls
, statistic
):
496 return cls(statistic
=cls
.__mapping
__[statistic
])
499 class CeilometerAlarmOperation(collections
.namedtuple(
500 "CeilometerAlarmOperation",
510 def from_gi_obj(cls
, operation
):
511 return cls(operation
=cls
.__mapping
__[operation
])
514 class CeilometerAlarmSeverity(collections
.namedtuple(
515 "CeilometerAlarmSeverity",
519 "MODERATE": "moderate",
520 "CRITICAL": "critical",
523 def from_gi_obj(cls
, severity
):
524 return cls(severity
=cls
.__mapping
__[severity
])
527 class CeilometerAlarmActions(collections
.namedtuple(
528 "CeilometerAlarmActions",
529 "ok alarm insufficient_data")):
531 def from_gi_obj(cls
, actions
):
533 ok
=[obj
.url
for obj
in actions
.ok
],
534 alarm
=[obj
.url
for obj
in actions
.alarm
],
535 insufficient_data
=[obj
.url
for obj
in actions
.insufficient_data
],