mirror of
https://github.com/camptocamp/odoo-cloud-platform.git
synced 2026-06-24 16:48:36 +00:00
feat: add prometheus + cloud_plateform azure
This commit is contained in:
@@ -43,6 +43,12 @@ class CloudPlatform(osv.osv_abstract):
|
|||||||
# it in cloud_platform_exoscale in V11
|
# it in cloud_platform_exoscale in V11
|
||||||
return ['exoscale']
|
return ['exoscale']
|
||||||
|
|
||||||
|
def _filestore_kinds(self):
|
||||||
|
# XXX for backward compatibility, we need this one here, move
|
||||||
|
# it in cloud_platform_exoscale in V11
|
||||||
|
return ['exoscale']
|
||||||
|
|
||||||
|
|
||||||
# XXX for backward compatibility, we need this one here, move
|
# XXX for backward compatibility, we need this one here, move
|
||||||
# it in cloud_platform_exoscale in V11
|
# it in cloud_platform_exoscale in V11
|
||||||
def _config_by_server_env_for_exoscale(self):
|
def _config_by_server_env_for_exoscale(self):
|
||||||
@@ -220,6 +226,86 @@ class CloudPlatform(osv.osv_abstract):
|
|||||||
"automatically set by the function 'install_exoscale()'."
|
"automatically set by the function 'install_exoscale()'."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _check_azure(self, cr, uid, environment_name, context=None):
|
||||||
|
params = self.env["ir.config_parameter"].sudo()
|
||||||
|
use_azure = params.get_param("ir_attachment.location") == AZURE_STORE_KIND.name
|
||||||
|
if environment_name in ("prod", "integration"):
|
||||||
|
# Labs instances use azure by default, but we don't want
|
||||||
|
# to enforce it in case we want to test something with a different
|
||||||
|
# storage. At your own risks!
|
||||||
|
assert use_azure, (
|
||||||
|
"azure must be used on production and integration instances. "
|
||||||
|
"It is activated by setting 'ir_attachment.location.' to 'azure'."
|
||||||
|
" The 'install()' function sets this option "
|
||||||
|
"automatically."
|
||||||
|
)
|
||||||
|
if use_azure:
|
||||||
|
key_sets = [
|
||||||
|
["AZURE_STORAGE_USE_AAD", "AZURE_STORAGE_ACCOUNT_URL"],
|
||||||
|
["AZURE_STORAGE_CONNECTION_STRING"],
|
||||||
|
[
|
||||||
|
"AZURE_STORAGE_ACCOUNT_NAME",
|
||||||
|
"AZURE_STORAGE_ACCOUNT_URL",
|
||||||
|
"AZURE_STORAGE_ACCOUNT_KEY",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
is_valid = False
|
||||||
|
for key_set in key_sets:
|
||||||
|
if all([os.environ.get(key) for key in key_set]):
|
||||||
|
is_valid = True
|
||||||
|
break
|
||||||
|
assert is_valid, (
|
||||||
|
"When ir_attachment.location is set to 'azure', "
|
||||||
|
"at least one of the following enviromnent variable set "
|
||||||
|
"is required : {}".format(
|
||||||
|
" or ".join(
|
||||||
|
[" + ".join([key for key in key_set]) for key_set in key_sets]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
storage_name = os.environ.get("AZURE_STORAGE_NAME", "")
|
||||||
|
if environment_name in ("prod", "integration", "labs"):
|
||||||
|
assert storage_name, (
|
||||||
|
"AZURE_STORAGE_NAME environment variable is required when "
|
||||||
|
"ir_attachment.location is 'azure'.\n"
|
||||||
|
"Normally, 'azure' is activated on labs, integration "
|
||||||
|
"and production, but should not be used in dev environment"
|
||||||
|
" (or using a dedicated dev bucket, never using the "
|
||||||
|
"integration/prod bucket).\n"
|
||||||
|
"If you don't actually need a bucket, change the"
|
||||||
|
" 'ir_attachment.location' parameter."
|
||||||
|
)
|
||||||
|
# A bucket name is defined under the following format
|
||||||
|
# ^[a-z]+\-[a-z]+\-\d+$
|
||||||
|
# Anything other than prod bucket must be suffixed with env name
|
||||||
|
#
|
||||||
|
# Use AZURE_STORAGE_NAME_UNSTRUCTURED to by-pass check
|
||||||
|
# on bucket name structure
|
||||||
|
if os.environ.get("AZURE_STORAGE_NAME_UNSTRUCTURED"):
|
||||||
|
return
|
||||||
|
prod_bucket = bool(re.match(r"^[a-z]+\-[a-z]+\-\d+$", storage_name))
|
||||||
|
if environment_name == "prod":
|
||||||
|
assert prod_bucket, (
|
||||||
|
"AZURE_STORAGE_NAME should match '^[a-z]+\\-[a-z]+\\-\\d+$', "
|
||||||
|
"we got: '%s'" % (storage_name,)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# if we are using the prod bucket on another instance
|
||||||
|
# such as an integration, we must be sure to be in read only!
|
||||||
|
assert not prod_bucket, (
|
||||||
|
"AZURE_STORAGE_NAME should not match '^[a-z]+\\-[a-z]+\\-\\d+$', "
|
||||||
|
"we got: '%s'" % (storage_name,)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif environment_name == "test":
|
||||||
|
# store in DB so we don't have files local to the host
|
||||||
|
assert params.get_param("ir_attachment.location") == "db", (
|
||||||
|
"In test instances, files must be stored in the database with "
|
||||||
|
"'ir_attachment.location' set to 'db'. This is "
|
||||||
|
"automatically set by the function 'install()'."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _check_redis(self, cr, uid, environment_name, context=None):
|
def _check_redis(self, cr, uid, environment_name, context=None):
|
||||||
if environment_name in ('prod', 'integration', 'labs', 'test'):
|
if environment_name in ('prod', 'integration', 'labs', 'test'):
|
||||||
assert is_true(os.environ.get('ODOO_SESSION_REDIS')), (
|
assert is_true(os.environ.get('ODOO_SESSION_REDIS')), (
|
||||||
@@ -262,6 +348,8 @@ class CloudPlatform(osv.osv_abstract):
|
|||||||
self._check_s3(cr, uid, environment_name, context)
|
self._check_s3(cr, uid, environment_name, context)
|
||||||
elif kind == 'ovh':
|
elif kind == 'ovh':
|
||||||
self._check_swift(cr, uid, environment_name, context)
|
self._check_swift(cr, uid, environment_name, context)
|
||||||
|
elif kind == 'azure':
|
||||||
|
self._check_azure(cr, uid, environment_name, context)
|
||||||
self._check_redis(cr, uid, environment_name, context)
|
self._check_redis(cr, uid, environment_name, context)
|
||||||
|
|
||||||
def _register_hook(self, cr):
|
def _register_hook(self, cr):
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Cloud Platform Azure
|
||||||
|
====================
|
||||||
|
|
||||||
|
Install addons specific to the Azure setup.
|
||||||
|
|
||||||
|
* The object storage is Azure blob storage
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2017-2021 Camptocamp SA
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Cloud Platform Azure",
|
||||||
|
"summary": "Addons required for the Camptocamp Cloud Platform on Azure",
|
||||||
|
"version": "15.0.1.0.0",
|
||||||
|
"author": "Camptocamp,Odoo Community Association (OCA)",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"category": "Extra Tools",
|
||||||
|
"depends": [
|
||||||
|
"cloud_platform",
|
||||||
|
"attachment_azure",
|
||||||
|
"monitoring_prometheus",
|
||||||
|
],
|
||||||
|
"excludes": [
|
||||||
|
"cloud_platform_ovh",
|
||||||
|
"cloud_platform_exoscale",
|
||||||
|
],
|
||||||
|
"website": "https://www.camptocamp.com",
|
||||||
|
"data": [],
|
||||||
|
"installable": True,
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from . import cloud_platform
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Copyright 2016-2021 Camptocamp SA
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
from openerp.osv import osv
|
||||||
|
from openerp.addons.cloud_platform.models.cloud_platform import FilestoreKind
|
||||||
|
from openerp.addons.cloud_platform.models.cloud_platform import PlatformConfig
|
||||||
|
|
||||||
|
AZURE_STORE_KIND = FilestoreKind("azure", "remote")
|
||||||
|
|
||||||
|
|
||||||
|
class CloudPlatform(osv.osv):
|
||||||
|
_inherit = "cloud.platform"
|
||||||
|
|
||||||
|
def _filestore_kinds(self):
|
||||||
|
kinds = super(CloudPlatform, self)._filestore_kinds()
|
||||||
|
kinds["azure"] = AZURE_STORE_KIND
|
||||||
|
return kinds
|
||||||
|
|
||||||
|
def _platform_kinds(self):
|
||||||
|
kinds = super(CloudPlatform, self)._platform_kinds()
|
||||||
|
kinds.append("azure")
|
||||||
|
return kinds
|
||||||
|
|
||||||
|
def _config_by_server_env_for_azure(self):
|
||||||
|
fs_kinds = self._filestore_kinds()
|
||||||
|
configs = {
|
||||||
|
"prod": PlatformConfig(filestore=fs_kinds["azure"]),
|
||||||
|
"integration": PlatformConfig(filestore=fs_kinds["azure"]),
|
||||||
|
"labs": PlatformConfig(filestore=fs_kinds["azure"]),
|
||||||
|
"test": PlatformConfig(filestore=fs_kinds["db"]),
|
||||||
|
"dev": PlatformConfig(filestore=fs_kinds["db"]),
|
||||||
|
}
|
||||||
|
return configs
|
||||||
|
|
||||||
|
def install_azure(self, cr, uid, context=None):
|
||||||
|
self.install(cr, uid, "azure", context)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:alt: License
|
||||||
|
|
||||||
|
==============================
|
||||||
|
Monitoring: Prometheus metrics
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Add an endpoint */metrics* to allow a Prometheus server to fetch application metrics.
|
||||||
|
Current available metrics are:
|
||||||
|
|
||||||
|
* Request completion time with 3 differentiators:
|
||||||
|
* Filestore
|
||||||
|
* Assets
|
||||||
|
* Everything else
|
||||||
|
* Longpolling request count
|
||||||
|
|
||||||
|
No additional configuration is needed, just ensure that the Prometheus server is allowed to communicate with Odoo
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Copyright 2016-2021 Camptocamp SA
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Monitoring: Prometheus Metrics",
|
||||||
|
"version": "15.0.1.0.0",
|
||||||
|
"author": "Camptocamp,Odoo Community Association (OCA)",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"category": "category",
|
||||||
|
"depends": [
|
||||||
|
"base",
|
||||||
|
"web",
|
||||||
|
"server_environment",
|
||||||
|
],
|
||||||
|
"website": "http://www.camptocamp.com",
|
||||||
|
"data": [],
|
||||||
|
"external_dependencies": {
|
||||||
|
"python": ["prometheus_client"],
|
||||||
|
},
|
||||||
|
"installable": True,
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from . import prometheus_metrics
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Copyright 2016-2021 Camptocamp SA
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||||
|
|
||||||
|
from openerp.http import Controller, route
|
||||||
|
from prometheus_client import generate_latest
|
||||||
|
|
||||||
|
|
||||||
|
class PrometheusController(Controller):
|
||||||
|
@route("/metrics", auth="public")
|
||||||
|
def metrics(self):
|
||||||
|
return generate_latest()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from . import ir_http
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Copyright 2016-2021 Camptocamp SA
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||||
|
|
||||||
|
from openerp import osv
|
||||||
|
from openerp.http import request
|
||||||
|
from prometheus_client import Summary, Counter
|
||||||
|
|
||||||
|
|
||||||
|
REQUEST_TIME = Summary(
|
||||||
|
"request_latency_sec", "Request response time in sec", ["query_type"]
|
||||||
|
)
|
||||||
|
LONGPOLLING_COUNT = Counter("longpolling", "Longpolling request count")
|
||||||
|
|
||||||
|
|
||||||
|
class IrHttp(osv.osv_abstract):
|
||||||
|
_inherit = "ir.http"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _dispatch(cls):
|
||||||
|
path_info = request.httprequest.environ.get("PATH_INFO")
|
||||||
|
|
||||||
|
if path_info.startswith("/longpolling/"):
|
||||||
|
LONGPOLLING_COUNT.inc()
|
||||||
|
return super(IrHttp, self)._dispatch()
|
||||||
|
|
||||||
|
if path_info.startswith("/metrics"):
|
||||||
|
return super(IrHttp, self)._dispatch()
|
||||||
|
|
||||||
|
if path_info.startswith("/web/static"):
|
||||||
|
label = "assets"
|
||||||
|
elif path_info.startswith("/web/content"):
|
||||||
|
label = "filestore"
|
||||||
|
else:
|
||||||
|
label = "client"
|
||||||
|
|
||||||
|
res = None
|
||||||
|
with REQUEST_TIME.labels(label).time():
|
||||||
|
res = super(IrHttp, self)._dispatch()
|
||||||
|
|
||||||
|
return res
|
||||||
Reference in New Issue
Block a user