215 lines
7.6 KiB
Python
215 lines
7.6 KiB
Python
# Copyright 2016 Brocade Communications System, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
#
|
|
# 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 os
|
|
|
|
from keystoneclient import exceptions
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from tacker._i18n import _LW
|
|
from tacker.agent.linux import utils as linux_utils
|
|
from tacker.common import log
|
|
from tacker.extensions import nfvo
|
|
from tacker.nfvo.drivers.vim import abstract_vim_driver
|
|
from tacker.vm import keystone
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
OPTS = [cfg.StrOpt('openstack', default='/etc/tacker/vim/fernet_keys',
|
|
help='Dir.path to store fernet keys.')]
|
|
|
|
# same params as we used in ping monitor driver
|
|
OPENSTACK_OPTS = [
|
|
cfg.StrOpt('count', default='1',
|
|
help=_('number of ICMP packets to send')),
|
|
cfg.StrOpt('timeout', default='1',
|
|
help=_('number of seconds to wait for a response')),
|
|
cfg.StrOpt('interval', default='1',
|
|
help=_('number of seconds to wait between packets'))
|
|
]
|
|
cfg.CONF.register_opts(OPTS, 'vim_keys')
|
|
cfg.CONF.register_opts(OPENSTACK_OPTS, 'vim_monitor')
|
|
|
|
|
|
def config_opts():
|
|
return [('vim_keys', OPTS), ('vim_monitor', OPENSTACK_OPTS)]
|
|
|
|
|
|
class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver):
|
|
"""Driver for OpenStack VIM
|
|
|
|
OpenStack driver handles interactions with local as well as
|
|
remote OpenStack instances. The driver invokes keystone service for VIM
|
|
authorization and validation. The driver is also responsible for
|
|
discovering placement attributes such as regions, availability zones
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.keystone = keystone.Keystone()
|
|
self.keystone.create_key_dir(CONF.vim_keys.openstack)
|
|
|
|
def get_type(self):
|
|
return 'openstack'
|
|
|
|
def get_name(self):
|
|
return 'OpenStack VIM Driver'
|
|
|
|
def get_description(self):
|
|
return 'OpenStack VIM Driver'
|
|
|
|
def authenticate_vim(self, vim_obj):
|
|
"""Validate VIM auth attributes
|
|
|
|
Initialize keystoneclient with provided authentication attributes.
|
|
"""
|
|
auth_url = vim_obj['auth_url']
|
|
auth_cred = vim_obj['auth_cred']
|
|
vim_project = vim_obj['vim_project']
|
|
keystone_version = self._validate_auth_url(auth_url)
|
|
|
|
if keystone_version not in auth_url:
|
|
vim_obj['auth_url'] = auth_url + '/' + keystone_version
|
|
if keystone_version == 'v3':
|
|
auth_cred['project_id'] = vim_project.get('id')
|
|
auth_cred['project_name'] = vim_project.get('name')
|
|
if 'project_domain_id' not in auth_cred:
|
|
auth_cred[
|
|
'project_domain_id'
|
|
] = CONF.keystone_authtoken.project_domain_id
|
|
if 'user_domain_id' not in auth_cred:
|
|
auth_cred[
|
|
'user_domain_id'
|
|
] = CONF.keystone_authtoken.user_domain_id
|
|
else:
|
|
auth_cred['tenant_id'] = vim_project.get('id')
|
|
auth_cred['tenant_name'] = vim_project.get('name')
|
|
# user_id is not supported in keystone v2
|
|
auth_cred.pop('user_id', None)
|
|
auth_cred['auth_url'] = vim_obj['auth_url']
|
|
return self._initialize_keystone(keystone_version, auth_cred)
|
|
|
|
def _validate_auth_url(self, auth_url):
|
|
try:
|
|
keystone_version = self.keystone.get_version(auth_url)
|
|
except Exception as e:
|
|
LOG.error(_('VIM Auth URL invalid'))
|
|
raise nfvo.VimConnectionException(message=e.message)
|
|
return keystone_version
|
|
|
|
def _initialize_keystone(self, version, auth):
|
|
ks_client = self.keystone.initialize_client(version=version, **auth)
|
|
return ks_client
|
|
|
|
def _find_regions(self, ks_client):
|
|
if ks_client.version == 'v2.0':
|
|
service_list = ks_client.services.list()
|
|
heat_service_id = None
|
|
for service in service_list:
|
|
if service.type == 'orchestration':
|
|
heat_service_id = service.id
|
|
endpoints_list = ks_client.endpoints.list()
|
|
region_list = [endpoint.region for endpoint in
|
|
endpoints_list if endpoint.service_id ==
|
|
heat_service_id]
|
|
else:
|
|
region_info = ks_client.regions.list()
|
|
region_list = [region.id for region in region_info]
|
|
return region_list
|
|
|
|
def discover_placement_attr(self, vim_obj, ks_client):
|
|
"""Fetch VIM placement information
|
|
|
|
Attributes can include regions, AZ.
|
|
"""
|
|
try:
|
|
regions_list = self._find_regions(ks_client)
|
|
except exceptions.Unauthorized as e:
|
|
LOG.warn(_("Authorization failed for user"))
|
|
raise nfvo.VimUnauthorizedException(message=e.message)
|
|
vim_obj['placement_attr'] = {'regions': regions_list}
|
|
return vim_obj
|
|
|
|
@log.log
|
|
def register_vim(self, vim_obj):
|
|
"""Validate and register VIM
|
|
|
|
Store VIM information in Tacker for
|
|
VNF placements
|
|
"""
|
|
ks_client = self.authenticate_vim(vim_obj)
|
|
self.discover_placement_attr(vim_obj, ks_client)
|
|
self.encode_vim_auth(vim_obj['id'], vim_obj['auth_cred'])
|
|
LOG.debug(_('VIM registration completed for %s'), vim_obj)
|
|
|
|
@log.log
|
|
def deregister_vim(self, vim_id):
|
|
"""Deregister VIM from NFVO
|
|
|
|
Delete VIM keys from file system
|
|
"""
|
|
self.delete_vim_auth(vim_id)
|
|
|
|
@log.log
|
|
def delete_vim_auth(self, vim_id):
|
|
"""Delete vim information
|
|
|
|
Delete vim key stored in file system
|
|
"""
|
|
LOG.debug(_('Attempting to delete key for vim id %s'), vim_id)
|
|
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
|
|
try:
|
|
os.remove(key_file)
|
|
LOG.debug(_('VIM key deleted successfully for vim %s'), vim_id)
|
|
except OSError:
|
|
LOG.warn(_('VIM key deletion unsuccessful for vim %s'), vim_id)
|
|
|
|
@log.log
|
|
def encode_vim_auth(self, vim_id, auth):
|
|
"""Encode VIM credentials
|
|
|
|
Store VIM auth using fernet key encryption
|
|
"""
|
|
fernet_key, fernet_obj = self.keystone.create_fernet_key()
|
|
encoded_auth = fernet_obj.encrypt(auth['password'].encode('utf-8'))
|
|
auth['password'] = encoded_auth
|
|
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
|
|
try:
|
|
with open(key_file, 'w') as f:
|
|
f.write(fernet_key.decode('utf-8'))
|
|
LOG.debug(_('VIM auth successfully stored for vim %s'), vim_id)
|
|
except IOError:
|
|
raise nfvo.VimKeyNotFoundException(vim_id=vim_id)
|
|
|
|
@log.log
|
|
def vim_status(self, auth_url):
|
|
"""Checks the VIM health status"""
|
|
vim_ip = auth_url.split("//")[-1].split(":")[0].split("/")[0]
|
|
ping_cmd = ['ping',
|
|
'-c', cfg.CONF.vim_monitor.count,
|
|
'-W', cfg.CONF.vim_monitor.timeout,
|
|
'-i', cfg.CONF.vim_monitor.interval,
|
|
vim_ip]
|
|
|
|
try:
|
|
linux_utils.execute(ping_cmd, check_exit_code=True)
|
|
return True
|
|
except RuntimeError:
|
|
LOG.warning(_LW("Cannot ping ip address: %s"), vim_ip)
|
|
return False
|