Skip to content

Commit

Permalink
RDS v3 SDK feature extension (#235)
Browse files Browse the repository at this point in the history
RDS v3 SDK feature extension

The following RDS v3 API functionalities have been added to the SDK module:

Instance actions: restart, change storage size, change flavour
Tag actions: addition and deletion of instance tags

Reviewed-by: Artem Goncharov <[email protected]>
Reviewed-by: Anton Sidelnikov <None>
Reviewed-by: None <None>
Reviewed-by: None <None>
  • Loading branch information
ameszi authored Oct 21, 2021
1 parent 83db556 commit cd05359
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 5 deletions.
5 changes: 4 additions & 1 deletion doc/source/sdk/proxies/rds_v3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ Instance Operations
:noindex:
:members: instances, get_instance, find_instance,
create_instance, delete_instance, restore_instance,
get_instance_restore_time
get_instance_restore_time, restart_instance,
enlarge_instance_volume, change_instance_flavor,
get_instance_logs, add_tag, remove_tag


Backup Operations
^^^^^^^^^^^^^^^^^
Expand Down
87 changes: 87 additions & 0 deletions otcextensions/sdk/rds/v3/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.

import datetime

from openstack import proxy
from openstack import resource

Expand Down Expand Up @@ -205,6 +207,91 @@ def set_instance_backup_policy(self, instance, keep_days,
return instance.set_backup_policy(self, keep_days=keep_days,
start_time=start_time, period=period)

def restart_instance(self, instance):
"""Restart the database instance
:param instance: This parameter can be either the ID of an instance
or a :class:`~openstack.sdk.rds.v3.instance.Instance`
"""
instance = self._get_resource(_instance.Instance, instance)
return instance.restart(self)

def enlarge_instance_volume(self, instance, size):
"""Enlarge the instance volume
:param instance: This parameter can be either the ID of an instance
or a :class:`~openstack.sdk.rds.v3.instance.Instance`
:param size:
"""
instance = self._get_resource(_instance.Instance, instance)
return instance.enlarge_volume(self, size)

def change_instance_flavor(self, instance, spec_code):
"""Change the instance's flavor.
:param instance: This parameter can be either the ID of an instance
or a :class:`~openstack.sdk.rds.v3.instance.Instance`
:param spec_code:
"""
instance = self._get_resource(_instance.Instance, instance)
return instance.update_flavor(self, spec_code)

def get_instance_logs(self, instance, log_type, start_date=None,
end_date=None, offset=1, limit=10, level='ALL'):
"""Get instance logs. If no dates are specified logs are gathered
from the last 24 hours.
:param instance: This parameter can be either the ID of an instance
or a :class:`~openstack.sdk.rds.v3.instance.Instance`
:param str log_type: The type of logs to query:
'errorlog' or 'slowlog'.
:param str start_date: Start date of the of the log query. Format:
%Y-%m-%dT%H:%M:%S%z where z is the tzinfo in HHMM format.
:param str end_date: End date of the of the log query. Format:
%Y-%m-%dT%H:%M:%S%z where z is the tzinfo in HHMM format.
:param int offset: Specifies the page offset such as 1, 2, 3 or 4.
:param int limit: Specifies the number of records on a page.
Its value range is from 1 to 100.
:param str level: Specifies the log level.
Values: ALL, INFO, LOG, WARNING, ERROR, FATAL, PANIC, NOTE.
"""
instance = self._get_resource(_instance.Instance, instance)
if log_type not in ['errorlog', 'slowlog']:
raise Exception('The parameter log_type has to be either '
'"errorlog" or "slowlog".')
if bool(start_date) ^ bool(end_date):
raise Exception('The parameters start_date and end_date should '
'only be specified together.')
elif not any([start_date, end_date]):
current_time = datetime.datetime.now().astimezone()
yesterday = current_time - datetime.timedelta(days=1)
start_date = yesterday.strftime("%Y-%m-%dT%H:%M:%S%z")
end_date = current_time.strftime("%Y-%m-%dT%H:%M:%S%z")

return instance.get_logs(self, log_type, start_date, end_date,
offset, limit, level)

def add_tag(self, instance, key, value):
"""Add tag to instance.
:param instance: This parameter can be either the ID of an instance
or a :class:`~openstack.sdk.rds.v3.instance.Instance`
:param str key: Name of the tag to be added.
:param str value: Value of the tag to be added.
"""
instance = self._get_resource(_instance.Instance, instance)
return instance.add_tag(self, key, value)

def remove_tag(self, instance, key):
"""Remove tag from instance.
:param instance: This parameter can be either the ID of an instance
or a :class:`~openstack.sdk.rds.v3.instance.Instance`
:param str key: Name of the tag to be removed.
"""
instance = self._get_resource(_instance.Instance, instance)
return instance.remove_tag(self, key)

# def get_instance_configuration(self, instance):
# """Obtaining a Configuration associated to instance.
#
Expand Down
88 changes: 84 additions & 4 deletions otcextensions/sdk/rds/v3/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ class Instance(_base.Resource):
#: indicating the reliability first and availability first, respectively.
#: *Type:string*
switch_strategy = resource.Body('switch_strategy')
#: Lists the tags and their values attached to the instance.
#: *Type:dict*
tags = resource.Body('tags', type=dict)
#: Time Zone.
#: *Type:string*
time_zone = resource.Body('time_zone')
Expand Down Expand Up @@ -206,7 +209,6 @@ def find(cls, session, name_or_id, ignore_missing=True, **params):
session = cls._get_session(session)

result = cls._find(session, name_or_id, id=name_or_id, **params)

if not result:
result = cls._find(session, name_or_id, name=name_or_id, **params)

Expand All @@ -226,6 +228,16 @@ def _find(cls, session, name_or_id, **params):
if result is not None:
return result

def _action(self, session, request_body):
url = utils.urljoin(self.base_path, self.id, 'action')
response = session.post(url, json=request_body)
exceptions.raise_from_response(response)

def _tag_action(self, session, request_body):
url = utils.urljoin(self.base_path, self.id, 'tags', 'action')
response = session.post(url, json=request_body)
exceptions.raise_from_response(response)

def fetch(self, session, requires_id=True,
base_path=None, error_message=None, **params):
"""Get a remote resource based on this instance.
Expand Down Expand Up @@ -266,10 +278,10 @@ def fetch_restore_times(self, session):
return self.restore_time

def get_instance_configuration(self, session):
pass
raise NotImplementedError()

def update_instance_configuration(self, session):
pass
def update_instance_configuration(self, session, values):
raise NotImplementedError()

def restore(self, session, backup=None, restore_time=None):
"""Restore instance from the backup of PIR.
Expand Down Expand Up @@ -322,3 +334,71 @@ def set_backup_policy(self, session, keep_days, start_time=None,
exceptions.raise_from_response(response)

return None

def get_logs(self, session, log_type, start_date, end_date,
offset, limit, level):
"""Get instance logs
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param str log_type: The type of logs to query:
'errorlog' or 'slowlog'.
:param str start_date: Start date of the of the log query. Format:
%Y-%m-%dT%H:%M:%S%z where z is the tzinfo in HHMM format.
:param str end_date: End date of the of the log query. Format:
%Y-%m-%dT%H:%M:%S%z where z is the tzinfo in HHMM format.
:param int offset: .
:param int limit: Specifies the number of records on a page. Its value
range is from 1 to 100.
:param str level: Specifies the log level.
"""
url_params = log_type + '?' + '&'.join([
'start_date=' + start_date,
'end_date=' + end_date,
'offset=' + str(offset),
'limit=' + str(limit),
'level=' + level
])
url = utils.urljoin(self.base_path, self.id, url_params)
response = session.get(url)
exceptions.raise_from_response(response)
return response.json()

def restart(self, session):
"""Restart the database instance
"""
self._action(session, {"restart": {}})

def enlarge_volume(self, session, size):
"""Enlarge the instance volume
"""
self._action(session, {"enlarge_volume": {"size": int(size)}})

def update_flavor(self, session, spec_code):
"""Chage the instance's flavor
"""
self._action(session, {"resize_flavor": {"spec_code": spec_code}})

def add_tag(self, session, key, value):
"""Add tag to instance
"""
request_body = {
"action": "create",
"tags": [{
"key": key,
"value": value
}]
}
self._tag_action(session, request_body)

def remove_tag(self, session, key):
"""Remove tag from instance
"""
request_body = {
"action": "delete",
"tags": [{
"key": key
}]
}
self._tag_action(session, request_body)
98 changes: 98 additions & 0 deletions otcextensions/tests/unit/sdk/rds/v3/test_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,101 @@ def test_set_backup_policy(self):
'19:00-20:00', 'period': '1,2'}})

self.assertIsNone(rt)

def test_tag_action(self):
sot = instance.Instance(**EXAMPLE)
tags = {
'action': 'create',
'tags': [{
'key': 'test_tag', 'value': 'test_value'
}]
}

response = mock.Mock()
response.status_code = 200
response.headers = {}
self.sess.post.return_value = response

rt = sot._tag_action(self.sess, tags)

self.sess.post.assert_called_with(
'instances/IDENTIFIER/tags/action',
json=tags)

self.assertIsNone(rt)

def test_add_tag(self):
sot = instance.Instance(**EXAMPLE)
test_tag_name = 'tagname'
test_tag_value = 'tagvalue'
sot._tag_action = mock.Mock()

rt = sot.add_tag(self.sess, test_tag_name, test_tag_value)

sot._tag_action.assert_called_with(
self.sess, {'action': 'create', 'tags':
[{'key': test_tag_name, 'value': test_tag_value}]})

self.assertIsNone(rt)

def test_remove_tag(self):
sot = instance.Instance(**EXAMPLE)
test_tag_name = 'tagname'
sot._tag_action = mock.Mock()

rt = sot.remove_tag(self.sess, test_tag_name)

sot._tag_action.assert_called_with(
self.sess, {'action': 'delete', 'tags': [{'key': 'tagname'}]})

self.assertIsNone(rt)

def test_action(self):
sot = instance.Instance(**EXAMPLE)
action = {"restart": {}}
response = mock.Mock()
response.status_code = 200
response.headers = {}
self.sess.post.return_value = response

rt = sot._action(self.sess, action)

self.sess.post.assert_called_with(
'instances/IDENTIFIER/action',
json=action)

self.assertIsNone(rt)

def test_restart(self):
sot = instance.Instance(**EXAMPLE)
sot._action = mock.Mock()

rt = sot.restart(self.sess)

sot._action.assert_called_with(self.sess, {'restart': {}})

self.assertIsNone(rt)

def test_enlarge_volume(self):
sot = instance.Instance(**EXAMPLE)
size = 200
sot._action = mock.Mock()

rt = sot.enlarge_volume(self.sess, size)

sot._action.assert_called_with(
self.sess, {'enlarge_volume': {'size': size}})

self.assertIsNone(rt)

def test_update_flavor(self):
sot = instance.Instance(**EXAMPLE)
flavor_spec_code = 'dummy.spec.code'
sot._action = mock.Mock()

rt = sot.update_flavor(self.sess, flavor_spec_code)

sot._action.assert_called_with(
self.sess, {'resize_flavor': {'spec_code': flavor_spec_code}})

self.assertIsNone(rt)
Loading

0 comments on commit cd05359

Please sign in to comment.