diff --git a/doc/source/sdk/proxies/waf.rst b/doc/source/sdk/proxies/waf.rst index ea6a880a2..2b2da8187 100644 --- a/doc/source/sdk/proxies/waf.rst +++ b/doc/source/sdk/proxies/waf.rst @@ -19,4 +19,10 @@ Certificate Operations :members: certificates, create_certificate, get_certificate, delete_certificate, update_certificate, find_certificate +Domain Operations +^^^^^^^^^^^^^^^^^ +.. autoclass:: otcextensions.sdk.waf.v1._proxy.Proxy + :noindex: + :members: domains, create_domain, get_domain, find_domain, + delete_domain, update_domain, find_domain diff --git a/doc/source/sdk/resources/index.rst b/doc/source/sdk/resources/index.rst index 93f3c977a..d1b01056f 100644 --- a/doc/source/sdk/resources/index.rst +++ b/doc/source/sdk/resources/index.rst @@ -20,6 +20,7 @@ Open Telekom Cloud Resources Object Block Storage (OBS) Relational Database Service (RDS) Virtual Private Cloud (VPC) + Web Application Firewall (WAF) Every resource which is used within the proxy methods have own attributes. Those attributes define the behavior of the resource which can be a cluster diff --git a/doc/source/sdk/resources/waf/index.rst b/doc/source/sdk/resources/waf/index.rst new file mode 100644 index 000000000..7f6aec6a8 --- /dev/null +++ b/doc/source/sdk/resources/waf/index.rst @@ -0,0 +1,8 @@ +WAF Resources +============= + +.. toctree:: + :maxdepth: 1 + + v1/certificate + v1/domain diff --git a/doc/source/sdk/resources/waf/v1/certificate.rst b/doc/source/sdk/resources/waf/v1/certificate.rst new file mode 100644 index 000000000..35a222c85 --- /dev/null +++ b/doc/source/sdk/resources/waf/v1/certificate.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.waf.v1.certificate +==================================== + +.. automodule:: otcextensions.sdk.waf.v1.certificate + +The WAF Certificate Class +------------------------- + +The ``Certificate`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.waf.v1.certificate.Certificate + :members: diff --git a/doc/source/sdk/resources/waf/v1/domain.rst b/doc/source/sdk/resources/waf/v1/domain.rst new file mode 100644 index 000000000..4c9ebadc4 --- /dev/null +++ b/doc/source/sdk/resources/waf/v1/domain.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.waf.v1.domain +=============================== + +.. automodule:: otcextensions.sdk.waf.v1.domain + +The WAF Domain Class +-------------------- + +The ``Domain`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.waf.v1.domain.Domain + :members: diff --git a/otcextensions/sdk/waf/v1/_base.py b/otcextensions/sdk/waf/v1/_base.py index 1cb544b8c..3620d33af 100644 --- a/otcextensions/sdk/waf/v1/_base.py +++ b/otcextensions/sdk/waf/v1/_base.py @@ -66,3 +66,118 @@ def find(cls, session, name_or_id, ignore_missing=True, **params): return None raise exceptions.ResourceNotFound( "No %s found for %s" % (cls.__name__, name_or_id)) + + @classmethod + def list(cls, session, paginated=True, base_path=None, + allow_unknown_params=False, **params): + """This method is a generator which yields resource objects. + + This resource object list generator handles pagination and takes query + params for response filtering. + + :param session: The session to use for making this request. + :type session: :class:`~keystoneauth1.adapter.Adapter` + :param bool paginated: ``True`` if a GET to this resource returns + a paginated series of responses, or ``False`` + if a GET returns only one page of data. + **When paginated is False only one + page of data will be returned regardless + of the API's support of pagination.** + :param str base_path: Base part of the URI for listing resources, if + different from :data:`~openstack.resource.Resource.base_path`. + :param bool allow_unknown_params: ``True`` to accept, but discard + unknown query parameters. This allows getting list of 'filters' and + passing everything known to the server. ``False`` will result in + validation exception when unknown query parameters are passed. + :param dict params: These keyword arguments are passed through the + :meth:`~openstack.resource.QueryParamter._transpose` method + to find if any of them match expected query parameters to be + sent in the *params* argument to + :meth:`~keystoneauth1.adapter.Adapter.get`. They are additionally + checked against the + :data:`~openstack.resource.Resource.base_path` format string + to see if any path fragments need to be filled in by the contents + of this argument. + + :return: A generator of :class:`Resource` objects. + :raises: :exc:`~openstack.exceptions.MethodNotSupported` if + :data:`Resource.allow_list` is not set to ``True``. + :raises: :exc:`~openstack.exceptions.InvalidResourceQuery` if query + contains invalid params. + """ + if not cls.allow_list: + raise exceptions.MethodNotSupported(cls, "list") + + if base_path is None: + base_path = cls.base_path + params = cls._query_mapping._validate( + params, base_path=base_path, + allow_unknown_params=allow_unknown_params) + query_params = cls._query_mapping._transpose(params, cls) + uri = base_path % params + + limit = query_params.get('limit', '10') + + # Track the total number of resources yielded so we can paginate + # swift objects + total_yielded = 0 + page = 0 + while uri: + # Copy query_params due to weird mock unittest interactions + response = session.get( + uri, + headers={"Accept": "application/json"}, + params=query_params.copy(), + ) + exceptions.raise_from_response(response) + data = response.json() + + # Discard any existing pagination keys + query_params.pop('marker', None) + query_params.pop('limit', None) + + if cls.resources_key: + resources = data[cls.resources_key] + else: + resources = data + + if not isinstance(resources, list): + resources = [resources] + + marker = None + for raw_resource in resources: + # Do not allow keys called "self" through. Glance chose + # to name a key "self", so we need to pop it out because + # we can't send it through cls.existing and into the + # Resource initializer. "self" is already the first + # argument and is practically a reserved word. + raw_resource.pop("self", None) + + value = cls.existing( + connection=session._get_connection(), + **raw_resource) + marker = value.id + yield value + total_yielded += 1 + + if resources and paginated: + page += 1 + uri, next_params = cls._get_next_link( + uri, response, data, marker, limit, total_yielded, page) + query_params.update(next_params) + else: + return + + @classmethod + def _get_next_link(cls, uri, response, data, marker, limit, total_yielded, + page): + next_link = None + params = {} + if total_yielded < data['total']: + next_link = uri + params['offset'] = page + params['limit'] = limit + else: + next_link = None + query_params = cls._query_mapping._transpose(params, cls) + return next_link, query_params diff --git a/otcextensions/sdk/waf/v1/_proxy.py b/otcextensions/sdk/waf/v1/_proxy.py index 9b3ec8014..2b03d25d2 100644 --- a/otcextensions/sdk/waf/v1/_proxy.py +++ b/otcextensions/sdk/waf/v1/_proxy.py @@ -12,6 +12,7 @@ from openstack import proxy from otcextensions.sdk.waf.v1 import certificate as _cert +from otcextensions.sdk.waf.v1 import domain as _domain class Proxy(proxy.Proxy): @@ -112,3 +113,88 @@ def find_certificate(self, name_or_id, ignore_missing=True, **attrs): return self._find(_cert.Certificate, name_or_id, ignore_missing=ignore_missing, **attrs) + + # ======== Domains ======== + def domains(self, **query): + """Retrieve a generator of domains + + :param dict query: Optional query parameters to be sent to limit the + resources being returned. + * `limit`: pagination limit + * `offset`: pagination offset + * `name`: domain name (hostname) + * `policy_name`: policy name + + :returns: A generator of domain + :class:`~otcextensions.sdk.waf.v1.domain.Domain` + instances + """ + return self._list(_domain.Domain, **query) + + def create_domain(self, **attrs): + """Upload domain from attributes + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~otcextensions.sdk.waf.v1.domain.Domain`, + comprised of the properties on the Domain class. + :returns: The results of domain creation + :rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain` + """ + return self._create(_domain.Domain, prepend_key=False, **attrs) + + def get_domain(self, domain): + """Get a domain + + :param domain: The value can be the ID of a domain + or a :class:`~otcextensions.sdk.waf.v1.domain.Domain` + instance. + :returns: Domain instance + :rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain` + """ + return self._get(_domain.Domain, domain) + + def delete_domain(self, domain, ignore_missing=True): + """Delete a domain + + :param domain: The value can be the ID of a domain + or a :class:`~otcextensions.sdk.waf.v1.domain.Domain` + instance. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the domain does not exist. + When set to ``True``, no exception will be set when attempting to + delete a nonexistent domain. + + :returns: Domain been deleted + :rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain` + """ + return self._delete(_domain.Domain, domain, + ignore_missing=ignore_missing) + + def update_domain(self, domain, **attrs): + """Update domain attributes + + :param domain: The id or an instance of + :class:`~otcextensions.sdk.waf.v1.domain.Domain` + :param dict attrs: attributes for update on + :class:`~otcextensions.sdk.waf.v1.domain.Domain` + + :rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain` + """ + return self._update(_domain.Domain, domain, **attrs) + + def find_domain(self, name_or_id, ignore_missing=True, **attrs): + """Find a single domain + + :param name_or_id: The name or ID of a domain + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised + when the domain does not exist. + When set to ``True``, no exception will be set when attempting + to delete a nonexistent domain. + + :returns: ``None`` + """ + return self._find(_domain.Domain, name_or_id, + ignore_missing=ignore_missing, + **attrs) diff --git a/otcextensions/sdk/waf/v1/domain.py b/otcextensions/sdk/waf/v1/domain.py new file mode 100644 index 000000000..432c77746 --- /dev/null +++ b/otcextensions/sdk/waf/v1/domain.py @@ -0,0 +1,86 @@ +# 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 openstack import resource + +from otcextensions.sdk.waf.v1 import _base + + +class ServerEntry(resource.Resource): + #: Properties + #: IP address + address = resource.Body('address') + #: Client protocol + client_protocol = resource.Body('client_protocol') + #: Server protocol + server_protocol = resource.Body('server_protocol') + #: Port number + port = resource.Body('port', type=int) + + +class Domain(_base.Resource): + """WAF Domain Resource""" + resources_key = 'items' + base_path = '/waf/instance' + + # capabilities + allow_create = True + allow_list = True + allow_fetch = True + allow_delete = True + allow_commit = True + + _query_mapping = resource.QueryParameters( + 'name', 'hostname', 'policy_name', 'limit', 'offset', + policy_name='policyname', + name='hostname') + + #: Properties + #: Specifies whether a domain name is connected to WAF + access_status = resource.Body('access_status', type=int) + #: Certificate ID. + #: This parameter is mandatory when client_protocol is set to HTTPS. + certificate_id = resource.Body('certificate_id') + #: CNAME + cname = resource.Body('cname') + #: domain name + name = resource.Body('hostname', aka='hostname') + #: Specifies the policy ID. + policy_id = resource.Body('policy_id') + #: protocol type of the client. + #: The options are HTTP, HTTPS, and HTTP,HTTPS. + protocol = resource.Body('protocol') + #: WAF mode. + protect_status = resource.Body('protect_status', type=int) + #: Specifies whether a proxy is configured. + proxy = resource.Body('proxy', type=bool) + #: Specifies the origin server information, including the client_protocol, + #: server_protocol, address, and port fields. + server = resource.Body('server', type=list, list_type=ServerEntry) + #: source IP header. This parameter is required only when proxy is set + #: to true. + #: The options are as follows: default, cloudflare, akamai, and custom. + sip_header_name = resource.Body('sip_header_name') + #: Specifies the HTTP request header for identifying the real source IP + #: address. This parameter is required only when proxy is set to true. + #: - If sip_header_name is default, sip_header_list is ["X-Forwarded-For"]. + #: - If sip_header_name is cloudflare, sip_header_list is + #: ["CF-Connecting-IP", "X-Forwarded-For"]. + #: - If sip_header_name is akamai, sip_header_list is ["True-Client-IP"]. + #: - If sip_header_name is custom, you can customize a value. + sip_header_list = resource.Body('sip_header_list', type=list) + #: subdomain name. + #: This parameter is returned only when proxy is set to true. + subdomain = resource.Body('sub_domain') + #: Certificate uploading timestamp + timestamp = resource.Body('timestamp') + #: TXT record. This parameter is returned only when proxy is set to true. + txt_record = resource.Body('txt_code') diff --git a/otcextensions/tests/functional/sdk/waf/v1/test_certificate.py b/otcextensions/tests/functional/sdk/waf/v1/test_certificate.py index 5a77764e7..b18746d89 100644 --- a/otcextensions/tests/functional/sdk/waf/v1/test_certificate.py +++ b/otcextensions/tests/functional/sdk/waf/v1/test_certificate.py @@ -66,14 +66,15 @@ class TestCertificate(TestWaf): uslYHnizLvYY6FaAdExE1TpM6YrM3b7aYMgv700CDsBCpFncQUx9tujpQxCmMoHZ rNcviQ== -----END CERTIFICATE-----""" - CERT_NAME = "SDK-" + uuid.uuid4().hex def setUp(self): super(TestCertificate, self).setUp() + + self.cert_name = "SDK-" + uuid.uuid4().hex self.cert = self.client.create_certificate( key=self._PRIVATE_KEY, content=self._CERTIFICATE, - name=self.CERT_NAME + name=self.cert_name ) self.addCleanup(self.conn.waf.delete_certificate, self.cert) @@ -99,13 +100,13 @@ def test_update_certificate(self): cert2 = self.client.create_certificate( key=self._PRIVATE_KEY, content=self._CERTIFICATE, - name=self.CERT_NAME + "_2" + name=self.cert_name + "_2" ) self.addCleanup(self.conn.waf.delete_certificate, cert2) cert2_cmp = self.client.update_certificate( cert2, - name=self.CERT_NAME + "_2_cp" + name=self.cert_name + "_2_cp" ) self.assertEqual(cert2.name, cert2_cmp.name) diff --git a/otcextensions/tests/functional/sdk/waf/v1/test_domain.py b/otcextensions/tests/functional/sdk/waf/v1/test_domain.py new file mode 100644 index 000000000..8b21aff6b --- /dev/null +++ b/otcextensions/tests/functional/sdk/waf/v1/test_domain.py @@ -0,0 +1,154 @@ +# 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 random +import uuid + +# from openstack import resource + +from otcextensions.tests.functional.sdk.waf import TestWaf + + +class TestDomain(TestWaf): + + _PRIVATE_KEY = """-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDrvw+CfkRMtN6I +KQK+YNKhjdWqUCnTI7YqZLDhZkoIqcvK1F2mjkcoGXOAjCjvGXf/xX35j0dGLgHK +e3AwNaQDPRWec6DuTqh9kBq9Qy7rUs6Na85wwSN8FG7z9XRuWR9NhEg24nrATUr/ +k5biBtHKiP22xI9nVws+IEoYVGOOjJ2CPt9XszaS/pdN6bQLchPSLOM5WN6BHCVn +u19RnSKFqCr8AmYx2Aqo30uFTHy1EhvSX8CnRTHNvWl7qciISqiNenXjIZyCe80n +7VB+LzSbm3HeqMDsM0euq0P5uPty2A6Uuo/TlPWlls6ZhFTP+AQ9H78kPWY9nKjl +1Ja4K0sbAgMBAAECggEANJ9oceOHkWvKRLCK2T45pjBH4oWUYHoXPq1NQnMX0Yk9 +YWA4K2aVAaF0w9wFgyG3RJOsBBn0efjpE26sY0aF/ucSvVToNmm+eJDDNz4Y6hSI +4M6QvWCPcDILdk9zFvKz5xTBHec+KVDXjec/BeMpz0D3CWYk8JdgfhStFXM46eeR +z1KBOq51x+I0VD3Ar4T3hfKG2IViwevC/7kghBw+D1U/c4stHFCXv4JlrhFET2I6 +kquGtV38fMUdWBLRVr0wBB4orm+9rpSlTvbnDuuEJcb8rKvrLkGraUhSTqepQD6M +lTN4BxY+3NqdnP/SKVBRoXr+gQsLdgPUAhkvTB8f0QKBgQD32mpyweaMZYTqZ8xF +xOBzjCTGVHNlXMMt8rz9+kJ4krJ77R3L07qf+mo5bsOB2ZybHhTy7+G6QO8TXyrI +60nbpoFR0eyWy6kdn4NtY/9BCcj13cV1D495zLr2HAveWDVVGJpLorkG5d674dtl +wD+B5EQIliCVR5GWMeciFGrewwKBgQDzfsU+EXlKAw6KMInyRP/+nWNk0PFir01H +Q4C/SrTM/Y8bCJ3/pWVAQsxEbQk1pOdWcdzHFf8BRncMA+OUDTxSCHJYiaqL+2pN +nNB3/bShocMKvDodJxXWMhdM2fMLFMtYCNsjr0DM8Cqvw7oZF8MY6oxM+uWzmI5R +nWKFMFXMyQKBgQDBK8PnKOSM69qJ7tgwUF827zUCNnOxvniIaTWPJOuFmZ/uIkIk +yCId6Ue892z82SPLacieBwQA6/bpPDTWXzszLDSCFoC0joqCAf6m1Vbt07iCl5P7 +xmLmZQAaLIW7hzgZ2JD4/hwDGklcWY1rYkic7dFwd8FxV1RKoR4pW4xnjQKBgQDf +nEbU9kUVg/MhUuwL8fPJxo3VstBKWUS1sjcU9S1Op3h5UhOPBzwRpIZkPGHdwr+0 +MkKXDgsuB6EiBpxDhVgk2Z7w0hQuE0gPWHhWCUaNvLkaLbuMtC0olL2zFOBPB9yp +zxA4GCSBT/lTioJnstu3EQahVzQFF49zQf6M49OXiQKBgCqOdwZjTH5gBnDSbWMM +WAFcxEzr5moG4nJzz/5sGqN5IRy1zDd/QkV2KEhjzWFbpGMgbgNTiLmz0BT6hUXl +/jS27B9AOPsdktyb88+ZuEfG6dYCmPnjBiOUrovbFk5IIAmiMAUT+W9HXN9shH0g +Ltxv392mcEGwmbfc1YJJfN2B +-----END PRIVATE KEY-----""" + + _CERTIFICATE = """-----BEGIN CERTIFICATE----- +MIIDADCCAegCCQCUu4mu6VfH/zANBgkqhkiG9w0BAQsFADBCMQswCQYDVQQGEwJE +RTELMAkGA1UEBwwCUEIxDDAKBgNVBAoMA1RTSTEYMBYGA1UEAwwPbXlmYWtlLnRl +c3QuY29tMB4XDTIwMDkwMTA5Mjc1M1oXDTIxMDkwMTA5Mjc1M1owQjELMAkGA1UE +BhMCREUxCzAJBgNVBAcMAlBCMQwwCgYDVQQKDANUU0kxGDAWBgNVBAMMD215ZmFr +ZS50ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOu/D4J+ +REy03ogpAr5g0qGN1apQKdMjtipksOFmSgipy8rUXaaORygZc4CMKO8Zd//FffmP +R0YuAcp7cDA1pAM9FZ5zoO5OqH2QGr1DLutSzo1rznDBI3wUbvP1dG5ZH02ESDbi +esBNSv+TluIG0cqI/bbEj2dXCz4gShhUY46MnYI+31ezNpL+l03ptAtyE9Is4zlY +3oEcJWe7X1GdIoWoKvwCZjHYCqjfS4VMfLUSG9JfwKdFMc29aXupyIhKqI16deMh +nIJ7zSftUH4vNJubcd6owOwzR66rQ/m4+3LYDpS6j9OU9aWWzpmEVM/4BD0fvyQ9 +Zj2cqOXUlrgrSxsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAcNsm1y3PgC47O7qW +3X531EiXmXsKuFWrpQeuVgSI/PrtXCn3/Gr1GcFQDA3k5iyDsApohwbyUcpXhA6c +842r2Flb11tMF7lxHwHGffryBeFbvCNSNYDvN9zA/XQfqpYi4UPPXPyLH0jVD0Ek +BCqJJFFzkRbUTcvTxCUxNEYpIQC8U4RSyWXg5kTu6302YjmWaNcP3bfL4II/ddI4 +WyGW6tZI2z7GTYPutWljmtfgEto2Y3FimtiGU+P/uB6SxlESzkGEvAfEduGlyxY8 +uslYHnizLvYY6FaAdExE1TpM6YrM3b7aYMgv700CDsBCpFncQUx9tujpQxCmMoHZ +rNcviQ== +-----END CERTIFICATE-----""" + + def setUp(self): + super(TestDomain, self).setUp() + + self.cert_name = "SDK-D" + uuid.uuid4().hex + self.domain_name = 'example-{0}.org'.format(random.randint(1, 10000)) + + self.cert = self.client.create_certificate( + key=self._PRIVATE_KEY, + content=self._CERTIFICATE, + name=self.cert_name + ) + + self.domain = self.client.create_domain( + name=self.domain_name, + certificate_id=self.cert.id, + server=[dict( + client_protocol="HTTPS", + server_protocol="HTTP", + address="1.2.3.4", + port="80")], + proxy=True, + sip_header_name="default", + sip_header_list=['X-Forwarded-For'] + ) + + # reverse order is super important + self.addCleanup(self.conn.waf.delete_certificate, self.cert) + self.addCleanup(self.conn.waf.delete_domain, self.domain) + + def test_list_domains(self): + cnt = 15 + # Pagination is so broken in WAF, that it makes sense to test it in + # real, and not in units + for i in range(0, cnt): + domain = self.client.create_domain( + name='%s.%s' % (i, self.domain_name), + server=[dict( + client_protocol="HTTP", + server_protocol="HTTP", + address="1.2.3.4", + port="80")], + proxy=False, + ) + + self.addCleanup(self.conn.waf.delete_domain, domain) + + query = {'limit': 3} + domains = list(self.client.domains(**query)) + self.assertEqual(len(domains), cnt + 1) + + query = {'limit': 1} + domains = list(self.client.domains(**query)) + self.assertEqual(len(domains), cnt + 1) + + def test_get_domain(self): + domain = self.client.get_domain(self.domain.id) + self.assertEqual(self.domain.name, domain.name) + self.assertEqual(self.domain.id, domain.id) + + def test_find_domain(self): + domain = self.client.find_domain(self.domain.name) + self.assertEqual(self.domain.name, domain.name) + self.assertEqual(self.domain.id, domain.id) + + def test_update_domain(self): + cert2 = self.client.create_certificate( + key=self._PRIVATE_KEY, + content=self._CERTIFICATE, + name=self.cert_name + "_2" + ) + + domain = self.client.update_domain( + domain=self.domain, + certificate_id=cert2.id, + ) + self.assertEqual(domain.id, self.domain.id) + self.addCleanup(self.conn.waf.delete_certificate, cert2) + + # We need to turn cert ref back, since otherwise cleanup can't drop + # what is being used + self.client.update_domain( + domain=self.domain, + certificate_id=self.cert.id, + ) diff --git a/otcextensions/tests/unit/sdk/waf/v1/test_certificate.py b/otcextensions/tests/unit/sdk/waf/v1/test_certificate.py new file mode 100644 index 000000000..e5f6fc72f --- /dev/null +++ b/otcextensions/tests/unit/sdk/waf/v1/test_certificate.py @@ -0,0 +1,65 @@ +# 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 keystoneauth1 import adapter + +from openstack.tests.unit import base + +from otcextensions.sdk.waf.v1 import certificate + + +FAKE_ID = "68d5745e-6af2-40e4-945d-fe449be00148" +EXAMPLE = { + "id": FAKE_ID, + "name": "fake_name", + "content": "fake", + "key": "fake", + "timestamp": 1499817600, + "expire_time": 1499817600 +} + + +class TestCertificate(base.TestCase): + + def setUp(self): + super(TestCertificate, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.post = mock.Mock() + + def test_basic(self): + sot = certificate.Certificate() + + self.assertEqual('/waf/certificate', sot.base_path) + self.assertEqual('items', sot.resources_key) + self.assertIsNone(sot.resource_key) + + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_commit) + + def test_make_it(self): + + sot = certificate.Certificate(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['name'], sot.name) + self.assertEqual(EXAMPLE['expire_time'], sot.expire_time) + self.assertEqual(EXAMPLE['timestamp'], sot.timestamp) + + self.assertDictEqual({ + 'limit': 'limit', + 'marker': 'marker', + 'offset': 'offset'}, + sot._query_mapping._mapping + ) diff --git a/otcextensions/tests/unit/sdk/waf/v1/test_domain.py b/otcextensions/tests/unit/sdk/waf/v1/test_domain.py new file mode 100644 index 000000000..568d758e4 --- /dev/null +++ b/otcextensions/tests/unit/sdk/waf/v1/test_domain.py @@ -0,0 +1,91 @@ +# 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 keystoneauth1 import adapter + +from openstack.tests.unit import base + +from otcextensions.sdk.waf.v1 import domain + + +FAKE_ID = "68d5745e-6af2-40e4-945d-fe449be00148" +EXAMPLE = { + "id": FAKE_ID, + "hostname": "www.b.com", + "cname": "3249d21e5eb34d21be12fdc817fcb67d.waf.cloud.com", + "txt_code": "3249d21e5eb34d21be12fdc817fcb67d", + "sub_domain": "3249d21e5eb34d21be12fdc817fcb67d.www.b.com", + "policy_id": "xxxxxxxxxxxxxx", + "certificate_id": "xxxxxxxxxxxxxxxxxxx", + "protect_status": 0, + "access_status": 0, + "protocol": "HTTP,HTTPS", + "server": [ + { + "client_protocol": "HTTPS", + "server_protocol": "HTTP", + "address": "X.X.X.X", + "port": 443 + }, { + "client_protocol": "HTTP", + "server_protocol": "HTTP", + "address": "X.X.X.X", + "port": 80 + } + ], + "proxy": True, + "sip_header_name": "default", + "sip_header_list": ["X-Forwarded-For"], + "timestamp": 1499817600 +} + + +class TestDomain(base.TestCase): + + def setUp(self): + super(TestDomain, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.post = mock.Mock() + + def test_basic(self): + sot = domain.Domain() + + self.assertEqual('/waf/instance', sot.base_path) + self.assertEqual('items', sot.resources_key) + self.assertIsNone(sot.resource_key) + + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_commit) + + def test_make_it(self): + + sot = domain.Domain(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['hostname'], sot.name) + self.assertEqual(EXAMPLE['hostname'], sot.hostname) + self.assertEqual(EXAMPLE['access_status'], sot.access_status) + self.assertEqual(EXAMPLE['certificate_id'], sot.certificate_id) + self.assertEqual(len(EXAMPLE['server']), len(sot.server)) + + self.assertDictEqual({ + 'hostname': 'hostname', + 'limit': 'limit', + 'marker': 'marker', + 'name': 'hostname', + 'offset': 'offset', + 'policy_name': 'policyname'}, + sot._query_mapping._mapping + ) diff --git a/otcextensions/tests/unit/sdk/waf/v1/test_proxy.py b/otcextensions/tests/unit/sdk/waf/v1/test_proxy.py index 2bcdf66d3..adc1a649c 100644 --- a/otcextensions/tests/unit/sdk/waf/v1/test_proxy.py +++ b/otcextensions/tests/unit/sdk/waf/v1/test_proxy.py @@ -12,6 +12,7 @@ from otcextensions.sdk.waf.v1 import _proxy from otcextensions.sdk.waf.v1 import certificate +from otcextensions.sdk.waf.v1 import domain from openstack.tests.unit import test_proxy_base @@ -47,3 +48,29 @@ def test_certificates(self): def test_certificate_update(self): self.verify_update(self.proxy.update_certificate, certificate.Certificate) + + +class TestWafDomain(TestWafProxy): + def test_domain_create(self): + self.verify_create(self.proxy.create_domain, + domain.Domain, + method_kwargs={'name': 'id'}, + expected_kwargs={'name': 'id', + 'prepend_key': False}) + + def test_domain_delete(self): + self.verify_delete(self.proxy.delete_domain, + domain.Domain, True) + + def test_domain_find(self): + self.verify_find(self.proxy.find_domain, domain.Domain) + + def test_domain_get(self): + self.verify_get(self.proxy.get_domain, domain.Domain) + + def test_domains(self): + self.verify_list(self.proxy.domains, domain.Domain) + + def test_domain_update(self): + self.verify_update(self.proxy.update_domain, + domain.Domain)