Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add put/get/delete alert_source APIs. #59

Merged
merged 6 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions dolphin/api/schemas/alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2020 The SODA Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from dolphin.api.validation import parameter_types


put = {
'type': 'object',
'properties': {
'host': parameter_types.hostname_or_ip_address,
'version': parameter_types.snmp_version,
'community_string': {'type': 'string', 'minLength': 1, 'maxLength': 255},
'username': {'type': 'string', 'minLength': 1, 'maxLength': 255},
'security_level': parameter_types.snmp_security_level,
'auth_key': {'type': 'string', 'minLength': 1, 'maxLength': 255},
'auth_protocol': parameter_types.snmp_auth_protocol,
'privacy_protocol': parameter_types.snmp_privacy_protocol,
'privacy_key': {'type': 'string', 'minLength': 1, 'maxLength': 255},
'engine_id': {'type': 'string', 'minLength': 1, 'maxLength': 255}
},
'required': ['host', 'version'],
'additionalProperties': False,
}

150 changes: 150 additions & 0 deletions dolphin/api/v1/alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Copyright 2020 The SODA Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from oslo_log import log
from webob import exc

from dolphin import db, cryptor
from dolphin import exception
from dolphin.api import validation
from dolphin.api.common import wsgi
from dolphin.api.schemas import alert as schema_alert
from dolphin.api.views import alert as alert_view
from dolphin.drivers import api as driver_api
from dolphin.i18n import _

LOG = log.getLogger(__name__)


SNMPv3_keys = ('username', 'auth_key', 'security_level', 'auth_protocol',
'privacy_protocol', 'privacy_key', 'engine_id')


class AlertController(wsgi.Controller):
def __init__(self):
super().__init__()
self.driver_api = driver_api.API()

@wsgi.response(200)
@validation.schema(schema_alert.put)
def put(self, req, id, body):
"""Create a new alert source or update an exist one."""
kumarashit marked this conversation as resolved.
Show resolved Hide resolved
ctx = req.environ['dolphin.context']
alert_source = body

try:
alert_source["storage_id"] = id
db.storage_get(ctx, id)
alert_source = self._input_check(alert_source)

if self._exist(ctx, id):
alert_source = db.alert_source_update(ctx, id, alert_source)
else:
alert_source = db.alert_source_create(ctx, alert_source)
except exception.StorageNotFound as e:
msg = (_("Alert source cannot be created or updated for a"
" non-existing storage %s.") % id)
raise exc.HTTPBadRequest(explanation=msg)
except exception.InvalidInput as e:
raise exc.HTTPBadRequest(explanation=e.msg)

return alert_view.build_alert_source(alert_source.to_dict())

@wsgi.response(200)
def show(self, req, id):
ctx = req.environ['dolphin.context']
try:
alert_source = db.alert_source_get(ctx, id)
except exception.AlertSourceNotFound as e:
raise exc.HTTPNotFound(explanation=e.msg)

return alert_view.build_alert_source(alert_source.to_dict())

@wsgi.response(200)
def delete(self, req, id):
ctx = req.environ['dolphin.context']
try:
db.alert_source_delete(ctx, id)
except exception.AlertSourceNotFound as e:
raise exc.HTTPNotFound(explanation=e.msg)

def _input_check(self, alert_source):
version = alert_source.get('version')

if version.lower() == 'snmpv3':
user_name = alert_source.get('username', None)
security_level = alert_source.get('security_level', None)
engine_id = alert_source.get('engine_id', None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If user selects v3 and gives just username and enginid, will it be success. Is it better to consider default auth_enabled_str as false

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, it will failed and the error message will tell the user auth_enabled is required. Cauz IMO, SNMPv3 is mainly for security, if auth is not enabled, then that will not be safe, so we'd better let the user to explicitly enable or disable authentication.

if not user_name or not security_level or not engine_id:
msg = "If snmp version is SNMPv3, then username, security_level" \
" and engine_id are required."
raise exception.InvalidInput(reason=msg)

if security_level == "AuthNoPriv" or security_level == "AuthPriv":
auth_protocol = alert_source.get('auth_protocol', None)
auth_key = alert_source.get('auth_key', None)
if not auth_protocol or not auth_key:
msg = "If snmp version is SNMPv3 and security_level is " \
"AuthPriv or AuthNoPriv, auth_protocol and auth_key" \
" are required."
raise exception.InvalidInput(reason=msg)
alert_source['auth_key'] = cryptor.encode(alert_source['auth_key'])

if security_level == "AuthPriv":
privacy_protocol = alert_source.get('privacy_protocol', None)
privacy_key = alert_source.get('privacy_key', None)
if not privacy_protocol or not privacy_key:
msg = "If snmp version is SNMPv3 and security_level is" \
"AuthPriv, privacy_protocol and privacy_key are " \
"required."
raise exception.InvalidInput(reason=msg)
alert_source['privacy_key'] = cryptor.encode(
alert_source['privacy_key'])

# Clear keys for other versions.
alert_source['community_string'] = None
else:
community_string = alert_source.get('community_string', None)
if not community_string:
msg = "If snmp version is SNMPv1 or SNMPv2c, " \
"community_string is required."
raise exception.InvalidInput(reason=msg)

# Clear keys for SNMPv3
for k in SNMPv3_keys:
alert_source[k] = None

return alert_source

def _exist(self, ctx, storage_id):
try:
db.alert_source_get(ctx, storage_id)
except exception.AlertSourceNotFound:
return False

return True

def _decrypt_auth_key(self, alert_source):
auth_key = alert_source.get('auth_key', None)
privacy_key = alert_source.get('privacy_key', None)
if auth_key:
alert_source['auth_key'] = cryptor.decode(auth_key)
if privacy_key:
alert_source['privacy_key'] = cryptor.decode(privacy_key)

return alert_source


def create_resource():
return wsgi.Resource(AlertController())
20 changes: 18 additions & 2 deletions dolphin/api/v1/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from dolphin.api import extensions
from dolphin.api import common
from dolphin.api import extensions
from dolphin.api import versions
from dolphin.api.v1 import storages
from dolphin.api.v1 import access_info
from dolphin.api.v1 import alert
from dolphin.api.v1 import pools
from dolphin.api.v1 import storages
from dolphin.api.v1 import volumes


Expand Down Expand Up @@ -49,6 +50,21 @@ def _setup_routes(self, mapper):
controller=self.resources['access_info'],
action="update",
conditions={"method": ["PUT"]})

self.resources['alert_sources'] = alert.create_resource()
mapper.connect("storages", "/storages/{id}/alert-source",
controller=self.resources['alert_sources'],
action="put",
conditions={"method": ["PUT"]})
mapper.connect("storages", "/storages/{id}/alert-source",
controller=self.resources['alert_sources'],
action="show",
conditions={"method": ["GET"]})
mapper.connect("storages", "/storages/{id}/alert-source",
controller=self.resources['alert_sources'],
action="delete",
conditions={"method": ["DELETE"]})

self.resources['pools'] = pools.create_resource()
mapper.resource("pool", "pools",
controller=self.resources['pools'])
Expand Down
19 changes: 19 additions & 0 deletions dolphin/api/validation/parameter_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,22 @@ def valid_char(char):
'minimum': 0, 'maximum': 65535
}

snmp_version = {
'type': 'string',
'enum': ['SNMPv1', 'SNMPv2c', 'SNMPv3', 'snmpv1', 'snmpv2c', 'snmpv3'],
}

snmp_auth_protocol = {
'type': 'string',
'enum': ['SHA', 'sha', 'MD5', 'md5'],
}

snmp_privacy_protocol = {
'type': 'string',
'enum': ['AES', 'aes', 'DES', 'des', '3DES', '3des'],
}

snmp_security_level = {
'type': 'string',
'enum': ['AuthPriv', 'AuthNoPriv', 'NoAuthNoPriv'],
}
21 changes: 21 additions & 0 deletions dolphin/api/views/alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2020 The SODA Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy


def build_alert_source(value):
view = copy.deepcopy(value)
view.pop("auth_key")
view.pop("privacy_key")
return dict(view)
20 changes: 20 additions & 0 deletions dolphin/db/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,23 @@ def access_info_get_all(context, marker=None, limit=None, sort_keys=None,
def is_orm_value(obj):
"""Check if object is an ORM field."""
return IMPL.is_orm_value(obj)


def alert_source_create(context, values):
"""Create an alert source."""
return IMPL.alert_source_create(context, values)


def alert_source_update(context, storage_id, values):
"""Update an alert source."""
return IMPL.alert_source_update(context, storage_id, values)


def alert_source_get(context, storage_id):
Copy link
Collaborator

@sushanthakumar sushanthakumar May 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to fetch alert source when we want to get storage id from source ip. So we need one alert source get all api with filter, do we have it already?

Copy link
Collaborator Author

@sfzeng sfzeng May 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR only include the user side functuion, that will be done in the next PR, otherwise PR will became too big.

"""Get an alert source."""
return IMPL.alert_source_get(context, storage_id)


def alert_source_delete(context, storage_id):
"""Delete an alert source."""
return IMPL.alert_source_delete(context, storage_id)
63 changes: 58 additions & 5 deletions dolphin/db/sqlalchemy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,22 @@

"""Implementation of SQLAlchemy backend."""

import six
import sys

import six
import sqlalchemy
from sqlalchemy import create_engine

from oslo_config import cfg
from oslo_db import options as db_options
from oslo_db.sqlalchemy import utils as db_utils
from oslo_db.sqlalchemy import session
from oslo_db.sqlalchemy import utils as db_utils
from oslo_log import log
from oslo_utils import uuidutils
from sqlalchemy import create_engine

from dolphin import exception
from dolphin.common import sqlalchemyutils
from dolphin.db.sqlalchemy import models
from dolphin.db.sqlalchemy.models import Storage, AccessInfo
from dolphin import exception
from dolphin.i18n import _

CONF = cfg.CONF
Expand Down Expand Up @@ -486,6 +485,60 @@ def model_query(context, model, *args, **kwargs):
model=model, session=session, args=args, **kwargs)


def alert_source_get(context, storage_id):
"""Get an alert source or raise an exception if it does not exist."""
return _alert_source_get(context, storage_id)


def _alert_source_get(context, storage_id, session=None):
result = (_alert_source_get_query(context, session=session)
.filter_by(storage_id=storage_id)
.first())

if not result:
raise exception.AlertSourceNotFound(storage_id=storage_id)

return result


def _alert_source_get_query(context, session=None):
return model_query(context, models.AlertSource, session=session)


def alert_source_create(context, values):
"""Add an alert source configuration."""
alert_source_ref = models.AlertSource()
alert_source_ref.update(values)

session = get_session()
with session.begin():
session.add(alert_source_ref)

return _alert_source_get(context,
alert_source_ref['storage_id'],
session=session)


def alert_source_update(context, storage_id, values):
"""Update an alert source configuration."""
session = get_session()
with session.begin():
_alert_source_get(context, storage_id, session).update(values)
return _alert_source_get(context, storage_id, session)


def alert_source_delete(context, storage_id):
session = get_session()
with session.begin():
query = _alert_source_get_query(context, session)
result = query.filter_by(storage_id=storage_id).delete()
if not result:
LOG.error("Cannot delete non-exist alert source[storage_id=%s].", storage_id)
raise exception.AlertSourceNotFound(storage_id=storage_id)
else:
LOG.info("Delete alert source[storage_id=%s] successfully.", storage_id)


PAGINATION_HELPERS = {
models.AccessInfo: (_access_info_get_query, _process_access_info_filters,
_access_info_get),
Expand Down
Loading