distcloud/dcorch/drivers/openstack/sysinv_v1.py

759 lines
28 KiB
Python

# 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 hashlib
import os
import six
import tsconfig.tsconfig as tsc
from cgtsclient import client as cgts_client
from cgtsclient.exc import HTTPConflict
from cgtsclient.exc import HTTPNotFound
from cgtsclient.v1.icommunity import CREATION_ATTRIBUTES \
as SNMP_COMMUNITY_CREATION_ATTRIBUTES
from cgtsclient.v1.itrapdest import CREATION_ATTRIBUTES \
as SNMP_TRAPDEST_CREATION_ATTRIBUTES
from oslo_log import log
from sysinv.common import constants as sysinv_constants
from dcorch.common import consts
from dcorch.common import exceptions
from dcorch.drivers import base
LOG = log.getLogger(__name__)
API_VERSION = '1'
def make_sysinv_patch(update_dict):
patch = []
for k, v in update_dict.iteritems():
key = k
if not k.startswith('/'):
key = '/' + key
p = {'path': key, 'value': v, 'op': 'replace'}
patch.append(dict(p))
LOG.debug("make_sysinv_patch patch={}".format(patch))
return patch
class SysinvClient(base.DriverBase):
"""Sysinv V1 driver."""
# TODO(John): This could go into cgtsclient/v1/remotelogging.py
REMOTELOGGING_PATCH_ATTRS = ['ip_address', 'enabled', 'transport', 'port',
'action']
def __init__(self, region_name, session):
self._expired = False
self.api_version = API_VERSION
self.region_name = region_name
self.session = session
self.client = self.update_client(
self.api_version, self.region_name, self.session)
def update_client(self, api_version, region_name, session):
try:
endpoint = self.session.get_endpoint(
service_type=consts.ENDPOINT_TYPE_PLATFORM,
interface=consts.KS_ENDPOINT_INTERNAL,
region_name=region_name)
token = session.get_token()
client = cgts_client.Client(
api_version,
endpoint=endpoint,
token=token)
except exceptions.ServiceUnavailable:
raise
self._expired = False
return client
def get_dns(self):
"""Get the dns nameservers for this region
:return: dns
"""
idnss = self.client.idns.list()
if not idnss:
LOG.info("dns is None for region: %s" % self.region_name)
return None
idns = idnss[0]
LOG.debug("get_dns uuid=%s nameservers=%s" %
(idns.uuid, idns.nameservers))
return idns
def update_dns(self, nameservers):
"""Update the dns nameservers for this region
:param: nameservers csv string
:return: Nothing
"""
try:
idns = self.get_dns()
if not idns:
LOG.warn("idns not found %s" % self.region_name)
return idns
if idns.nameservers != nameservers:
if nameservers == "":
nameservers = "NC"
patch = make_sysinv_patch({'nameservers': nameservers,
'action': 'apply'})
LOG.info("region={} dns update uuid={} patch={}".format(
self.region_name, idns.uuid, patch))
idns = self.client.idns.update(idns.uuid, patch)
else:
LOG.info("update_dns no changes, skip dns region={} "
"update uuid={} nameservers={}".format(
self.region_name, idns.uuid, nameservers))
except Exception as e:
LOG.error("update_dns exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return idns
def get_ntp(self):
"""Get the ntp configuration for this region
:return: ntp
"""
intps = self.client.intp.list()
if not intps:
LOG.info("ntp is None for region: %s" % self.region_name)
return None
intp = intps[0]
LOG.debug("get_ntp uuid=%s enabled=%s ntpservers=%s" %
(intp.uuid, intp.enabled, intp.ntpservers))
return intp
@staticmethod
def _same_ntpservers(i1, i2):
same_ntpservers = True
if i1 != i2:
if not i1 and not i2:
# To catch equivalent ntpservers None vs ""
same_ntpservers = True
else:
same_ntpservers = False
return same_ntpservers
def update_ntp(self, enabled, ntpservers):
"""Update the ntpservers for this region
:param: enabled string
:param: ntpservers csv string
:return: Nothing
"""
try:
intp = self.get_ntp()
if not intp:
LOG.warn("intp not found %s" % self.region_name)
return intp
if ntpservers == "NC":
ntpservers = ""
if str(intp.enabled) != enabled or \
not self._same_ntpservers(intp.ntpservers, ntpservers):
if ntpservers == "":
ntpservers = "NC"
patch = make_sysinv_patch({'enabled': enabled,
'ntpservers': ntpservers,
'action': 'apply'})
LOG.info("region={} ntp update uuid={} patch={}".format(
self.region_name, intp.uuid, patch))
intp = self.client.intp.update(intp.uuid, patch)
else:
LOG.info("update_ntp no changes, skip ntp region={} "
"update uuid={} enabled={} ntpservers={}".format(
self.region_name, intp.uuid, enabled, ntpservers))
except Exception as e:
LOG.error("update_ntp exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return intp
def get_ptp(self):
"""Get the ptp configuration for this region
:return: ptp
"""
ptps = self.client.ptp.list()
if not ptps:
LOG.info("ptp is None for region: %s" % self.region_name)
return None
ptp = ptps[0]
LOG.debug("get_ptp uuid=%s enabled=%s mode=%s "
"transport=%s mechanism=%s" %
(ptp.uuid, ptp.enabled, ptp.mode,
ptp.transport, ptp.mechanism))
return ptp
def update_ptp(self, enabled, mode, transport, mechanism):
"""Update the ptp configuration for this region
:param: enabled
:param: mode
:param: transport
:param: mechanism
:return: Nothing
"""
try:
ptp = self.get_ptp()
if not ptp:
LOG.warn("ptp not found %s" % self.region_name)
return ptp
if str(ptp.enabled) != enabled or \
ptp.mode != mode or \
ptp.transport != transport or \
ptp.mechanism != mechanism:
patch = make_sysinv_patch({'enabled': enabled,
'mode': mode,
'transport': transport,
'mechanism': mechanism})
LOG.info("region={} ptp update uuid={} patch={}".format(
self.region_name, ptp.uuid, patch))
ptp = self.client.ptp.update(ptp.uuid, patch)
else:
LOG.info("update_ptp no changes, skip ptp region={} "
"update uuid={} enabled={} mode={} "
"transport={} mechanism={}".format(
self.region_name, ptp.uuid,
enabled, mode, transport, mechanism))
except Exception as e:
LOG.error("update_ptp exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return ptp
def snmp_trapdest_list(self):
"""Get the trapdest list for this region
:return: itrapdests list of itrapdest
"""
itrapdests = self.client.itrapdest.list()
return itrapdests
def snmp_trapdest_create(self, trapdest_dict):
"""Add the trapdest for this region
:param: trapdest_payload dictionary
:return: itrapdest
"""
# Example trapdest_dict:
# {"ip_address": "10.10.10.12", "community": "cgcs"}
itrapdest = None
trapdest_create_dict = {}
for k, v in trapdest_dict.iteritems():
if k in SNMP_TRAPDEST_CREATION_ATTRIBUTES:
trapdest_create_dict[str(k)] = v
LOG.info("snmp_trapdest_create driver region={}"
"trapdest_create_dict={}".format(
self.region_name, trapdest_create_dict))
try:
itrapdest = self.client.itrapdest.create(**trapdest_create_dict)
except HTTPConflict:
LOG.info("snmp_trapdest_create exists region={}"
"trapdest_dict={}".format(
self.region_name, trapdest_dict))
# Retrieve the existing itrapdest
trapdests = self.snmp_trapdest_list()
for trapdest in trapdests:
if trapdest.ip_address == trapdest_dict.get('ip_address'):
LOG.info("snmp_trapdest_create found existing {}"
"for region: {}".format(
trapdest, self.region_name))
itrapdest = trapdest
break
except Exception as e:
LOG.error("snmp_trapdest_create exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return itrapdest
def snmp_trapdest_delete(self, trapdest_ip_address):
"""Delete the trapdest for this region
:param: trapdest_ip_address
"""
try:
LOG.info("snmp_trapdest_delete region {} ip_address: {}".format(
self.region_name, trapdest_ip_address))
self.client.itrapdest.delete(trapdest_ip_address)
except HTTPNotFound:
LOG.info("snmp_trapdest_delete NotFound %s for region: {}".format(
trapdest_ip_address, self.region_name))
raise exceptions.TrapDestNotFound(region_name=self.region_name,
ip_address=trapdest_ip_address)
except Exception as e:
LOG.error("snmp_trapdest_delete exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
def snmp_community_list(self):
"""Get the community list for this region
:return: icommunitys list of icommunity
"""
icommunitys = self.client.icommunity.list()
return icommunitys
def snmp_community_create(self, community_dict):
"""Add the community for this region
:param: community_payload dictionary
:return: icommunity
"""
# Example community_dict: {"community": "cgcs"}
icommunity = None
community_create_dict = {}
for k, v in community_dict.iteritems():
if k in SNMP_COMMUNITY_CREATION_ATTRIBUTES:
community_create_dict[str(k)] = v
LOG.info("snmp_community_create driver region={}"
"community_create_dict={}".format(
self.region_name, community_create_dict))
try:
icommunity = self.client.icommunity.create(**community_create_dict)
except HTTPConflict:
LOG.info("snmp_community_create exists region={}"
"community_dict={}".format(
self.region_name, community_dict))
# Retrieve the existing icommunity
communitys = self.snmp_community_list()
for community in communitys:
if community.community == community_dict.get('community'):
LOG.info("snmp_community_create found existing {}"
"for region: {}".format(
community, self.region_name))
icommunity = community
break
except Exception as e:
LOG.error("snmp_community_create exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return icommunity
def snmp_community_delete(self, community):
"""Delete the community for this region
:param: community
"""
try:
LOG.info("snmp_community_delete region {} community: {}".format(
self.region_name, community))
self.client.icommunity.delete(community)
except HTTPNotFound:
LOG.info("snmp_community_delete NotFound %s for region: {}".format(
community, self.region_name))
raise exceptions.CommunityNotFound(region_name=self.region_name,
community=community)
except Exception as e:
LOG.error("snmp_community_delete exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
def get_remotelogging(self):
"""Get the remotelogging for this region
:return: remotelogging
"""
try:
remoteloggings = self.client.remotelogging.list()
remotelogging = remoteloggings[0]
except Exception as e:
LOG.error("get_remotelogging exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
if not remotelogging:
LOG.info("remotelogging is None for region: %s" % self.region_name)
else:
LOG.debug("get_remotelogging uuid=%s ip_address=%s" %
(remotelogging.uuid, remotelogging.ip_address))
return remotelogging
def create_remote_logging_patch_from_dict(self, values):
patch = {}
action_found = False
for k, v in values.iteritems():
if k in self.REMOTELOGGING_PATCH_ATTRS:
if k == 'action':
action_found = True
elif k == 'enabled' and not isinstance(v, six.string_types):
# api requires a string for enabled
if not v:
patch[k] = 'false'
else:
patch[k] = 'true'
elif k == 'ip_address' and not v:
# api requires a non None/empty value
continue
else:
patch[k] = v
if not action_found:
patch['action'] = 'apply'
patch = make_sysinv_patch(patch)
LOG.debug("create_remote_logging_patch_from_dict=%s" % patch)
return patch
@staticmethod
def ip_address_in_patch(patch):
for p in patch:
if p['path'] == '/ip_address':
if p['value']:
return True
LOG.info("No valid ip_address_in_patch: %s" % patch)
return False
def update_remotelogging(self, values):
"""Update the remotelogging values for this region
:param: values dictionary or payload
:return: remotelogging
"""
try:
remotelogging = self.get_remotelogging()
if not remotelogging:
LOG.warn("remotelogging not found %s" % self.region_name)
return remotelogging
if isinstance(values, dict):
patch = self.create_remote_logging_patch_from_dict(values)
else:
patch = values
if (not self.ip_address_in_patch(patch) and
not remotelogging.ip_address):
# This region does not have an ip_address set yet
LOG.info("region={} remotelogging ip_address not set "
"uuid={} patch={}. Skip patch operation.".format(
self.region_name, remotelogging.uuid, patch))
return remotelogging
LOG.info("region={} remotelogging update uuid={} patch={}".format(
self.region_name, remotelogging.uuid, patch))
remotelogging = self.client.remotelogging.update(
remotelogging.uuid, patch)
except Exception as e:
LOG.error("update_remotelogging exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return remotelogging
def get_firewallrules(self):
"""Get the firewallrules for this region
:return: firewallrules
"""
try:
firewallruless = self.client.firewallrules.list()
firewallrules = firewallruless[0]
except Exception as e:
LOG.error("get_firewallrules region={} "
"exception={}".format(self.region_name, e))
raise exceptions.SyncRequestFailedRetry()
if not firewallrules:
LOG.info("firewallrules is None for region: {}".format(
self.region_name))
else:
LOG.info("get_firewallrules uuid=%s firewall_sig=%s" %
(firewallrules.uuid, firewallrules.firewall_sig))
return firewallrules
def _validate_firewallrules(self, firewall_sig, firewallrules):
firewallrules_sig = hashlib.md5(firewallrules).hexdigest()
if firewallrules_sig == firewall_sig:
return True
LOG.info("_validate_firewallrules region={} sig={} mismatch "
"reference firewall_sig={}".format(
self.region_name, firewallrules_sig, firewall_sig))
return False
def update_firewallrules(self,
firewall_sig,
firewallrules=None):
"""Update the firewallrules for this region
:param: firewall_sig
:param: firewallrules
:return: ifirewallrules
"""
if not firewallrules:
# firewallrules not provided, obtain from SystemController
firewall_rules_file = os.path.join(
tsc.CONFIG_PATH,
sysinv_constants.FIREWALL_RULES_FILE)
with open(firewall_rules_file, 'r') as content_file:
firewallrules = content_file.read()
LOG.info("update_firewallrules from shared file={}".format(
firewallrules))
if not self._validate_firewallrules(firewall_sig, firewallrules):
raise exceptions.SyncRequestFailedRetry()
try:
ifirewallrules = self.client.firewallrules.import_firewall_rules(
firewallrules)
LOG.info("region={} firewallrules uuid={} firewall_sig={}".format(
self.region_name, ifirewallrules.get('uuid'), firewall_sig))
except Exception as e:
LOG.error("update_firewallrules exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return ifirewallrules
def get_certificates(self):
"""Get the certificates for this region
:return: certificates
"""
try:
certificates = self.client.certificate.list()
except Exception as e:
LOG.error("get_certificates region={} "
"exception={}".format(self.region_name, e))
raise exceptions.SyncRequestFailedRetry()
if not certificates:
LOG.info("No certificates in region: {}".format(
self.region_name))
return certificates
def _validate_certificate(self, signature, certificate):
# JKUNG need to look at the crypto public serial id
certificate_sig = hashlib.md5(certificate).hexdigest()
if certificate_sig == signature:
return True
LOG.info("_validate_certificate region={} sig={} mismatch "
"reference signature={}".format(
self.region_name, certificate_sig, signature))
return False
def update_certificate(self,
signature,
certificate=None,
data=None):
"""Update the certificate for this region
:param: signature of the public certificate
:param: certificate
:param: data
:return: icertificate
"""
LOG.info("update_certificate signature {} data {}".format(
signature, data))
if not certificate:
tpmconfigs = self.client.tpmconfig.list()
if tpmconfigs:
LOG.info("region={} no certificates available, "
"tpm configured".format(self.region_name))
return
if data:
data['passphrase'] = None
mode = data.get('mode', sysinv_constants.CERT_MODE_SSL)
if mode == sysinv_constants.CERT_MODE_SSL_CA:
certificate_files = [sysinv_constants.SSL_CERT_CA_FILE]
elif mode == sysinv_constants.CERT_MODE_SSL:
certificate_files = [sysinv_constants.SSL_PEM_FILE]
elif mode == sysinv_constants.CERT_MODE_MURANO_CA:
certificate_files = [sysinv_constants.MURANO_CERT_CA_FILE]
elif mode == sysinv_constants.CERT_MODE_MURANO:
certificate_files = [sysinv_constants.MURANO_CERT_KEY_FILE,
sysinv_constants.MURANO_CERT_FILE]
else:
LOG.warn("update_certificate mode {} not supported".format(
mode))
return
elif signature and signature.startswith(
sysinv_constants.CERT_MODE_SSL_CA):
data['mode'] = sysinv_constants.CERT_MODE_SSL_CA
certificate_files = [sysinv_constants.SSL_CERT_CA_FILE]
elif signature and signature.startswith(
sysinv_constants.CERT_MODE_SSL):
data['mode'] = sysinv_constants.CERT_MODE_SSL
certificate_files = [sysinv_constants.SSL_PEM_FILE]
elif signature and signature.startswith(
sysinv_constants.CERT_MODE_MURANO_CA):
data['mode'] = sysinv_constants.CERT_MODE_MURANO_CA
certificate_files = [sysinv_constants.MURANO_CERT_CA_FILE]
elif signature and signature.startswith(
sysinv_constants.CERT_MODE_MURANO + '_'):
data['mode'] = sysinv_constants.CERT_MODE_MURANO
certificate_files = [sysinv_constants.MURANO_CERT_KEY_FILE,
sysinv_constants.MURANO_CERT_FILE]
else:
LOG.warn("update_certificate signature {} "
"not supported".format(signature))
return
certificate = ""
for certificate_file in certificate_files:
with open(certificate_file, 'r') as content_file:
certificate += content_file.read()
LOG.info("update_certificate from shared file {} {}".format(
signature, certificate_files))
if (signature and
(signature.startswith(sysinv_constants.CERT_MODE_SSL) or
(signature.startswith(sysinv_constants.CERT_MODE_TPM)))):
# ensure https is enabled
isystem = self.client.isystem.list()[0]
https_enabled = isystem.capabilities.get('https_enabled', False)
if not https_enabled:
isystem = self.client.isystem.update(
isystem.uuid,
[{"path": "/https_enabled",
"value": "true",
"op": "replace"}])
LOG.info("region={} enabled https system={}".format(
self.region_name, isystem.uuid))
try:
icertificate = self.client.certificate.certificate_install(
certificate, data)
LOG.info("update_certificate region={} signature={}".format(
self.region_name,
signature))
except Exception as e:
LOG.error("update_certificate exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return icertificate
def get_user(self):
"""Get the user password info for this region
:return: iuser
"""
iusers = self.client.iuser.list()
if not iusers:
LOG.info("user is None for region: %s" % self.region_name)
return None
iuser = iusers[0]
LOG.debug("get_user uuid=%s passwd_hash=%s" %
(iuser.uuid, iuser.passwd_hash))
return iuser
def update_user(self, passwd_hash, root_sig, passwd_expiry_days):
"""Update the user passwd for this region
:param: passwd_hash
:return: iuser
"""
try:
iuser = self.get_user()
if not iuser:
LOG.warn("iuser not found %s" % self.region_name)
return iuser
if (iuser.passwd_hash != passwd_hash or
iuser.passwd_expiry_days != passwd_expiry_days):
patch = make_sysinv_patch(
{'passwd_hash': passwd_hash,
'passwd_expiry_days': passwd_expiry_days,
'root_sig': root_sig,
'action': 'apply',
})
LOG.info("region={} user update uuid={} patch={}".format(
self.region_name, iuser.uuid, patch))
iuser = self.client.iuser.update(iuser.uuid, patch)
else:
LOG.info("update_user no changes, skip user region={} "
"update uuid={} passwd_hash={}".format(
self.region_name, iuser.uuid, passwd_hash))
except Exception as e:
LOG.error("update_user exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return iuser
def create_fernet_repo(self, key_list):
"""Add the fernet keys for this region
:param: key list payload
:return: Nothing
"""
# Example key_list:
# [{"id": 0, "key": "GgDAOfmyr19u0hXdm5r_zMgaMLjglVFpp5qn_N4GBJQ="},
# {"id": 1, "key": "7WfL_z54p67gWAkOmQhLA9P0ZygsbbJcKgff0uh28O8="},
# {"id": 2, "key": ""5gsUQeOZ2FzZP58DN32u8pRKRgAludrjmrZFJSOHOw0="}]
LOG.info("create_fernet_repo driver region={} "
"fernet_repo_list={}".format(self.region_name, key_list))
try:
self.client.fernet.create(key_list)
except Exception as e:
LOG.error("create_fernet_repo exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
def update_fernet_repo(self, key_list):
"""Update the fernet keys for this region
:param: key list payload
:return: Nothing
"""
LOG.info("update_fernet_repo driver region={} "
"fernet_repo_list={}".format(self.region_name, key_list))
try:
self.client.fernet.put(key_list)
except Exception as e:
LOG.error("update_fernet_repo exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
def get_fernet_keys(self):
"""Retrieve the fernet keys for this region
:return: a list of fernet keys
"""
try:
keys = self.client.fernet.list()
except Exception as e:
LOG.error("get_fernet_keys exception={}".format(e))
raise exceptions.SyncRequestFailedRetry()
return keys