Support multiple CA certificates synchronization in DC

This update enhanced dcorch and sysinv API proxy to support multiple CA
certificates synchronization in DC system. The support utilizes the
updated sysinv certificate install API and the new certificate
uninstall API.

Closes-Bug: 1861438
Closes-Bug: 1860995
Depends-On: https://review.opendev.org/#/c/711538/
Change-Id: I407314b913ae5a56bb714b39484aea3263a41d19
Signed-off-by: Andy Ning <andy.ning@windriver.com>
This commit is contained in:
Andy Ning 2020-02-26 16:04:33 -05:00
parent 99899f78d3
commit 1e588cbefa
6 changed files with 129 additions and 35 deletions

View File

@ -1,4 +1,4 @@
SRC_DIR="."
COPY_LIST="$FILES_BASE/*"
TIS_PATCH_VER=0
TIS_PATCH_VER=1

View File

@ -400,10 +400,19 @@ class SysinvAPIController(APIController):
# certificate need special processing
p_resource_info = 'suppressed'
if resource_type == consts.RESOURCE_TYPE_SYSINV_CERTIFICATE:
resource_info['payload'] = request_body
resource_info['content_type'] = environ.get('CONTENT_TYPE')
resource = json.loads(response.body)[resource_type]
resource_id = resource['signature']
if operation_type == consts.OPERATION_TYPE_DELETE:
resource_id = json.loads(response.body)['signature']
resource_ids = [resource_id]
else:
resource_info['payload'] = request_body
resource_info['content_type'] = environ.get('CONTENT_TYPE')
resource = json.loads(response.body)[resource_type]
# For ssl_ca cert, the resource in response is a list
if isinstance(resource, list):
resource_ids = [str(res.get('signature'))
for res in resource]
else:
resource_ids = [resource.get('signature')]
else:
if (operation_type == consts.OPERATION_TYPE_POST and
resource_type in self.RESOURCE_ID_MAP):
@ -413,22 +422,23 @@ class SysinvAPIController(APIController):
resource_id = json.loads(request_body)[rid]
else:
resource_id = self.get_resource_id_from_link(request_header)
resource_ids = [resource_id]
if operation_type != consts.OPERATION_TYPE_DELETE:
resource_info['payload'] = json.loads(request_body)
p_resource_info = resource_info
LOG.info("Resource id: (%s), type: (%s), info: (%s)",
resource_id, resource_type, p_resource_info)
try:
utils.enqueue_work(self.ctxt,
self.ENDPOINT_TYPE,
resource_type,
resource_id,
operation_type,
json.dumps(resource_info))
except exception.ResourceNotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
for resource_id in resource_ids:
LOG.info("Resource id: (%s), type: (%s), info: (%s)",
resource_id, resource_type, p_resource_info)
try:
utils.enqueue_work(self.ctxt,
self.ENDPOINT_TYPE,
resource_type,
resource_id,
operation_type,
json.dumps(resource_info))
except exception.ResourceNotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
class IdentityAPIController(APIController):

View File

@ -91,7 +91,8 @@ COMMUNITY_STRING_PATHS = [
]
CERTIFICATE_PATHS = [
'/v1/certificate/certificate_install'
'/v1/certificate/certificate_install',
'/v1/certificate/{uuid}'
]
USER_PATHS = [
@ -315,7 +316,7 @@ ROUTE_METHOD_MAP = {
consts.RESOURCE_TYPE_SYSINV_DNS: ['PATCH', 'PUT'],
consts.RESOURCE_TYPE_SYSINV_SNMP_TRAPDEST: ['POST', 'DELETE'],
consts.RESOURCE_TYPE_SYSINV_SNMP_COMM: ['POST', 'DELETE'],
consts.RESOURCE_TYPE_SYSINV_CERTIFICATE: ['POST'],
consts.RESOURCE_TYPE_SYSINV_CERTIFICATE: ['POST', 'DELETE'],
consts.RESOURCE_TYPE_SYSINV_USER: ['PATCH', 'PUT'],
},
consts.ENDPOINT_TYPE_NETWORK: {

View File

@ -226,3 +226,8 @@ class CommunityAlreadyExists(Conflict):
class CommunityNotFound(NotFound):
message = _("Community %(community)s not found in region=%(region_name)s")
class CertificateNotFound(NotFound):
message = _("Certificate in region=%(region_name)s with signature "
"%(signature)s not found")

View File

@ -370,6 +370,21 @@ class SysinvClient(base.DriverBase):
return icertificate
def delete_certificate(self, certificate):
"""Delete the certificate for this region
:param: a CA certificate to delete
"""
try:
LOG.info(" delete_certificate region {} certificate: {}".format(
self.region_name, certificate.signature))
self.client.certificate.certificate_uninstall(certificate.uuid)
except HTTPNotFound:
LOG.info("delete_certificate NotFound {} for region: {}".format(
certificate.signature, self.region_name))
raise exceptions.CertificateNotFound(
region_name=self.region_name, signature=certificate.signature)
def get_user(self):
"""Get the user password info for this region

View File

@ -24,6 +24,7 @@ from dcorch.common import exceptions
from dcorch.drivers.openstack import sdk_platform as sdk
from dcorch.engine.fernet_key_manager import FERNET_REPO_MASTER_ID
from dcorch.engine.fernet_key_manager import FernetKeyManager
from dcorch.engine.sync_thread import AUDIT_RESOURCE_EXTRA
from dcorch.engine.sync_thread import AUDIT_RESOURCE_MISSING
from dcorch.engine.sync_thread import SyncThread
@ -336,15 +337,15 @@ class SysinvSyncThread(SyncThread):
metadata))
return certificate, metadata
def sync_certificates(self, s_os_client, request, rsrc):
LOG.info("sync_certificate resource_info={}".format(
def create_certificate(self, s_os_client, request, rsrc):
LOG.info("create_certificate resource_info={}".format(
request.orch_job.resource_info),
extra=self.log_extra)
certificate_dict = jsonutils.loads(request.orch_job.resource_info)
payload = certificate_dict.get('payload')
if not payload:
LOG.info("sync_certificate No payload found in resource_info"
LOG.info("create_certificate No payload found in resource_info"
"{}".format(request.orch_job.resource_info),
extra=self.log_extra)
return
@ -359,7 +360,7 @@ class SysinvSyncThread(SyncThread):
certificate, metadata = self._decode_certificate_payload(
certificate_dict)
isignature = None
icertificate = None
signature = rsrc.master_id
if signature and signature != self.CERTIFICATE_SIG_NULL:
icertificate = self.update_certificate(
@ -367,9 +368,6 @@ class SysinvSyncThread(SyncThread):
signature,
certificate=certificate,
data=metadata)
cert_body = icertificate.get('certificates')
if cert_body:
isignature = cert_body.get('signature')
else:
LOG.info("skipping signature={}".format(signature))
@ -377,10 +375,69 @@ class SysinvSyncThread(SyncThread):
subcloud_rsrc_id = self.persist_db_subcloud_resource(
rsrc.id, signature)
LOG.info("certificate {} {} [{}/{}] updated".format(rsrc.id,
subcloud_rsrc_id, isignature, signature),
cert_bodys = icertificate.get('certificates')
sub_certs_updated = [str(cert_body.get('signature'))
for cert_body in cert_bodys]
LOG.info("certificate {} {} [{}] updated with subcloud certificates:"
" {}".format(rsrc.id, subcloud_rsrc_id, signature,
sub_certs_updated),
extra=self.log_extra)
def delete_certificate(self, s_os_client, request, rsrc):
subcloud_rsrc = self.get_db_subcloud_resource(rsrc.id)
if not subcloud_rsrc:
return
try:
certificates = s_os_client.sysinv_client.get_certificates()
cert_to_delete = None
for certificate in certificates:
if certificate.signature == subcloud_rsrc.subcloud_resource_id:
cert_to_delete = certificate
break
if not cert_to_delete:
raise exceptions.CertificateNotFound(
region_name=self.subcloud_engine.subcloud.region_name,
signature=subcloud_rsrc.subcloud_resource_id)
s_os_client.sysinv_client.delete_certificate(cert_to_delete)
except exceptions.CertificateNotFound:
# Certificate already deleted in subcloud, carry on.
LOG.info("Certificate not in subcloud, may be already deleted",
extra=self.log_extra)
except (AttributeError, TypeError) as e:
LOG.info("delete_certificate error {}".format(e),
extra=self.log_extra)
raise exceptions.SyncRequestFailedRetry
subcloud_rsrc.delete()
# Master Resource can be deleted only when all subcloud resources
# are deleted along with corresponding orch_job and orch_requests.
LOG.info("Certificate {}:{} [{}] deleted".format(
rsrc.id, subcloud_rsrc.id,
subcloud_rsrc.subcloud_resource_id),
extra=self.log_extra)
def sync_certificates(self, s_os_client, request, rsrc):
switcher = {
consts.OPERATION_TYPE_POST: self.create_certificate,
consts.OPERATION_TYPE_CREATE: self.create_certificate,
consts.OPERATION_TYPE_DELETE: self.delete_certificate,
}
func = switcher[request.orch_job.operation_type]
try:
func(s_os_client, request, rsrc)
except (keystone_exceptions.connection.ConnectTimeout,
keystone_exceptions.ConnectFailure) as e:
LOG.info("sync_certificates: subcloud {} is not reachable [{}]"
.format(self.subcloud_engine.subcloud.region_name,
str(e)), extra=self.log_extra)
raise exceptions.SyncRequestTimeout
except Exception as e:
LOG.exception(e)
raise exceptions.SyncRequestFailedRetry
def update_user(self, s_os_client, passwd_hash,
root_sig, passwd_expiry_days):
LOG.info("update_user={} {} {}".format(
@ -774,15 +831,15 @@ class SysinvSyncThread(SyncThread):
# resource can be either from dcorch DB or
# fetched by OpenStack query
resource_id = self.get_resource_id(resource_type, resource)
if resource_id == self.CERTIFICATE_SIG_NULL:
LOG.info("No certificate resource to sync")
return num_of_audit_jobs
elif resource_id == self.RESOURCE_UUID_NULL:
LOG.info("No resource to sync")
return num_of_audit_jobs
if finding == AUDIT_RESOURCE_MISSING:
# default action is create for a 'missing' resource
if resource_id == self.CERTIFICATE_SIG_NULL:
LOG.info("No certificate resource to sync")
return num_of_audit_jobs
elif resource_id == self.RESOURCE_UUID_NULL:
LOG.info("No resource to sync")
return num_of_audit_jobs
self.schedule_work(
self.endpoint_type, resource_type,
resource_id,
@ -791,6 +848,12 @@ class SysinvSyncThread(SyncThread):
resource_type, resource,
consts.OPERATION_TYPE_CREATE))
num_of_audit_jobs += 1
elif finding == AUDIT_RESOURCE_EXTRA:
# default action is delete for a 'extra' resource
self.schedule_work(self.endpoint_type, resource_type,
resource_id,
consts.OPERATION_TYPE_DELETE)
num_of_audit_jobs += 1
return num_of_audit_jobs
else: # use default audit_action
return super(SysinvSyncThread, self).audit_action(