Skip to content

Commit

Permalink
css fixes for cert download and snapshot policy config (#509)
Browse files Browse the repository at this point in the history
css fixes for cert download and snapshot policy config

Reviewed-by: Anton Sidelnikov
Reviewed-by: Vineet Pruthi
  • Loading branch information
vineet-pruthi authored Jan 24, 2025
1 parent a7a3c86 commit 5eacede
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 77 deletions.
6 changes: 6 additions & 0 deletions doc/source/cli/css.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ CSS Snapshot Operations

.. autoprogram-cliff:: openstack.css.v1
:command: css snapshot *

CSS SSL Certificate
-------------------

.. autoprogram-cliff:: openstack.css.v1
:command: css certificate *
7 changes: 7 additions & 0 deletions doc/source/sdk/proxies/css.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ Cluster Snapshot Operations
:members: snapshots, find_snapshot, create_snapshot, get_snapshot_policy,
set_snapshot_policy, set_snapshot_configuration,
disable_snapshot_function, restore_snapshot, delete_snapshot

Cluster SSL Certificate
^^^^^^^^^^^^^^^^^^^^^^^

.. autoclass:: otcextensions.sdk.css.v1._proxy.Proxy
:noindex:
:members: download_certificate
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,32 @@
# under the License.
#
'''CSS ELK cluster v1 action implementations'''
import base64
import logging

from osc_lib import utils
from osc_lib.command import command

from otcextensions.common import sdk_utils
from otcextensions.i18n import _

LOG = logging.getLogger(__name__)


def _get_columns(item):
column_map = {}
return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)


class DownloadCert(command.Command):
class DownloadCertificate(command.Command):
_description = _('Download the HTTPS certificate file of the server.')

def get_parser(self, prog_name):
parser = super(DownloadCert, self).get_parser(prog_name)
parser = super(DownloadCertificate, self).get_parser(prog_name)
parser.add_argument(
'--out',
metavar='<out>',
required=True,
help=_(
'Name of the output file where certificate will be saved.\n'
'Note: the file will be overwritten if it already exists'
'Name of the output file where certificate will be saved. '
'(Optional)'
),
)
return parser

def take_action(self, parsed_args):
client = self.app.client_manager.css

obj = client.get_certificate()
display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
with open(parsed_args.out, 'wb') as cert_file:
cert_file.write(base64.b64decode(data.cert_base64))
filename = None
if parsed_args.out:
filename = parsed_args.out
client.download_certificate(filename)
22 changes: 22 additions & 0 deletions otcextensions/osclient/css/v1/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@
LOG = logging.getLogger(__name__)


FREQUENCY_CHOICES = [
"day",
"hour",
"sun",
"mon",
"tue",
"wed",
"thu",
"fri",
"sat"
]


def _get_columns(item):
column_map = {}
hidden = [
Expand Down Expand Up @@ -299,6 +312,13 @@ def get_parser(self, prog_name):
metavar='<cluster>',
help=_('ID or Name of the cluster to which the snapshot belongs.'),
)
parser.add_argument(
"--frequency",
metavar="{" + ",".join(FREQUENCY_CHOICES) + "}",
type=lambda s: s.lower(),
choices=FREQUENCY_CHOICES,
help=_("Frequency of automatically creating snapshots."),
)
parser.add_argument(
'--name-prefix',
metavar='<name_prefix>',
Expand Down Expand Up @@ -354,6 +374,8 @@ def take_action(self, parsed_args):
attrs['enable'] = 'false'
if getattr(parsed_args, 'delete_auto'):
attrs['deleteAuto'] = 'true'
if parsed_args.frequency:
attrs['frequency'] = parsed_args.frequency.upper()

cluster = client.find_cluster(parsed_args.cluster)
client.set_snapshot_policy(cluster, **attrs)
Expand Down
54 changes: 33 additions & 21 deletions otcextensions/sdk/css/v1/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@
# 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 os
import time
from urllib.parse import urlsplit

from openstack import exceptions
from openstack import proxy
from otcextensions.sdk.css.v1 import cert as _cert
from otcextensions.sdk.css.v1 import cluster as _cluster
from otcextensions.sdk.css.v1 import cluster_image as _cluster_image
from otcextensions.sdk.css.v1 import (
Expand Down Expand Up @@ -274,15 +273,8 @@ def scale_in_cluster_by_node_type(self, cluster, nodes):
and quantity of nodes to remove
:returns: ``None``
"""
split_url = urlsplit(self.get_endpoint())
project_id = self.session.get_project_id()
self.endpoint_override = (
f'{split_url.scheme}://{split_url.netloc}/v1.0/extend/{project_id}'
)

cluster = self._get_resource(_cluster.Cluster, cluster)
resp = cluster.scale_in_by_node_type(self, nodes)
self.endpoint_override = None
return resp

def replace_cluster_node(self, cluster, node_id):
Expand Down Expand Up @@ -509,18 +501,38 @@ def restore_snapshot(self, cluster, snapshot, **attrs):
snapshot = self._get_resource(_snapshot.Snapshot, snapshot)
return snapshot.restore(self, cluster.id, **attrs)

def get_certificate(self):
"""Download the HTTPS certificate file of the server."""
split_url = urlsplit(self.get_endpoint())
self.endpoint_override = (
f'{split_url.scheme}://{split_url.netloc}/v1.0/'
)
resp = self._get(
_cert.Cert,
requires_id=False,
)
self.endpoint_override = None
return resp
def download_certificate(self, filename=None):
"""
Downloads a security certificate for ssl connectivity.
The certificate is downloaded and saved to the specified file.
:param filename: The name of the file to save the certificate as.
If not provided, the default filename 'CloudSearchService.cer'
is used.
:returns: ``None``
"""
headers = {'Accept': '*/*'}
response = self.get('/cer/download', headers=headers, stream=True)
exceptions.raise_from_response(response)

# Extract the filename from Content-Disposition header if available
content_disposition = response.headers.get('Content-Disposition', '')

override_filename = filename
filename = override_filename or 'CloudSearchService.cer'
if not override_filename and 'filename=' in content_disposition:
filename = content_disposition.split('filename=')[1].strip('"')

if os.path.exists(filename):
raise FileExistsError(
f"The file '{filename}' already exists. Aborting download."
)

with open(filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)

def wait_for_cluster(
self, cluster, timeout=1200, wait=5, print_status=False
Expand Down
21 changes: 0 additions & 21 deletions otcextensions/sdk/css/v1/cert.py

This file was deleted.

7 changes: 4 additions & 3 deletions otcextensions/sdk/css/v1/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,10 +338,11 @@ def scale_in_by_node_type(self, session, nodes):
'reducedNodeNum': reduced_node_num,
}
)
endpoint = session.get_endpoint().replace("v1.0", "v1.0/extend")

body = {'shrink': data}

self._action(session, 'role/shrink', body)
url = utils.urljoin(endpoint, 'clusters', self.id, 'role/shrink')
response = session.post(url, json={'shrink': data})
exceptions.raise_from_response(response)

def replace_node(self, session, node_id):
"""Replacing cluster node."""
Expand Down
2 changes: 2 additions & 0 deletions otcextensions/sdk/css/v1/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ class SnapshotPolicy(resource.Resource):
#: Whether to delete all automatically created snapshots when the
#: automatic snapshot creation policy is disabled.
delete_auto = resource.Body('deleteAuto', type=otc_format.BoolStr_1)
#: Frequency of automatically creating snapshots.
frequency = resource.Body('frequency')
#: Snapshot name prefix.
prefix = resource.Body('prefix')
#: Name of the index to be backed up.
Expand Down
37 changes: 37 additions & 0 deletions otcextensions/tests/unit/osclient/css/v1/test_certificate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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 mock

from otcextensions.tests.unit.osclient.css.v1 import fakes
from otcextensions.osclient.css.v1 import certificate


class TestDownloadCertificate(fakes.TestCss):

def setUp(self):
super(TestDownloadCertificate, self).setUp()

self.cmd = certificate.DownloadCertificate(self.app, None)
self.client.download_certificate = mock.Mock(return_value=None)

def test_download_certificate(self):
arglist = []

verifylist = []

# Verify cm is triggered with default parameters
parsed_args = self.check_parser(self.cmd, arglist, verifylist)

result = self.cmd.take_action(parsed_args)

self.assertIsNone(result)
4 changes: 4 additions & 0 deletions otcextensions/tests/unit/osclient/css/v1/test_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ def test_setpolicy(self):
'2',
'--period',
'3',
'--frequency',
'DAY',
'--disable',
'--delete-auto',
]
Expand All @@ -374,6 +376,7 @@ def test_setpolicy(self):
('name_prefix', '1'),
('keep_days', 2),
('period', '3'),
('frequency', 'day'),
('disable', True),
('delete_auto', True),
]
Expand All @@ -386,6 +389,7 @@ def test_setpolicy(self):
"prefix": "1",
"keepday": 2,
"period": "3",
"frequency": "DAY",
"enable": 'false',
"deleteAuto": 'true',
}
Expand Down
17 changes: 14 additions & 3 deletions otcextensions/tests/unit/sdk/css/v1/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,24 @@ def test_scale_in(self):

def test_scale_in_by_node_type(self):
sot = cluster.Cluster.existing(id=CLUSTER_ID)
sot._action = mock.Mock()
nodes = [{'type': 'ess', 'reducedNodeNum': 1}]
json_body = {'shrink': nodes}
response = mock.Mock()
response.status_code = 200
response.headers = {}
self.sess.post.return_value = response

body = {'shrink': nodes}
endpoint = 'https://test-url.com/v1.0/project-id'
updated_endpoint = 'https://test-url.com/v1.0/extend/project-id'

self.sess.get_endpoint.return_value = endpoint

rt = sot.scale_in_by_node_type(self.sess, nodes)
sot._action.assert_called_with(self.sess, 'role/shrink', body)
self.sess.post.assert_called_with(
f'{updated_endpoint}/clusters/{CLUSTER_ID}/role/shrink',
json=json_body
)

self.assertIsNone(rt)

def test_replace_node(self):
Expand Down
27 changes: 20 additions & 7 deletions otcextensions/tests/unit/sdk/css/v1/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

from openstack.tests.unit import test_proxy_base
from otcextensions.sdk.css.v1 import _proxy
from otcextensions.sdk.css.v1 import cert as _cert
from otcextensions.sdk.css.v1 import cluster as _cluster
from otcextensions.sdk.css.v1 import cluster_image as _cluster_image
from otcextensions.sdk.css.v1 import (
Expand Down Expand Up @@ -372,10 +371,24 @@ def test_get_snapshot_policy(self):
},
)

def test_get_certificate(self):
self._verify(
'openstack.proxy.Proxy._get',
self.proxy.get_certificate,
expected_args=[_cert.Cert],
expected_kwargs={'requires_id': False},
@mock.patch('builtins.open', new_callable=mock.mock_open)
@mock.patch('otcextensions.sdk.css.v1._proxy.Proxy.get')
def test_download_certificate(self, mock_get, mock_open):
mock_response = mock.Mock()
mock_response.status_code = 200
mock_response.headers = {
'Content-Disposition': 'attachment; filename="custom_cert.cer"'
}
mock_response.iter_content = mock.Mock(return_value=[b'cert content'])
mock_get.return_value = mock_response

filename = 'custom_cert.cer'
self.proxy.download_certificate(filename)

mock_get.assert_called_once_with(
'/cer/download',
headers={'Accept': '*/*'},
stream=True
)
mock_open.assert_called_once_with(filename, 'wb')
mock_open().write.assert_called_once_with(b'cert content')
2 changes: 2 additions & 0 deletions otcextensions/tests/unit/sdk/css/v1/test_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
'enable': 'true',
'indeices': '*',
'snapshotCmkId': uuid.uuid4().hex,
'frequency': 'DAY',
}


Expand Down Expand Up @@ -156,6 +157,7 @@ def test_make_it(self):
sot = snapshot.SnapshotPolicy(**EXAMPLE_POLICY)
self.assertEqual(EXAMPLE_POLICY['agency'], sot.agency)
self.assertEqual(EXAMPLE_POLICY['basePath'], sot.backup_path)
self.assertEqual(EXAMPLE_POLICY['frequency'], sot.frequency)
self.assertEqual(EXAMPLE_POLICY['period'], sot.backup_period)
self.assertEqual(EXAMPLE_POLICY['keepday'], sot.backup_keep_days)
self.assertEqual(EXAMPLE_POLICY['bucket'], sot.bucket_name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
Updated the following methods in the CSS module:
- Fixed CSS certificate download method.
- Removed the `override_endpoint` logic from the `scale_in_by_node_type` method.
- Added a `frequency` parameter to the snapshot policy configuration.
Loading

0 comments on commit 5eacede

Please sign in to comment.