Implement multisite VIM feature

Added support for multisite OpenStack VIM feature through a new
'nfvo' extension. This also enables remote VNF life cycle manage-
ment through the VIM Id parameter.

Implements: blueprint multi-site-vim
Depends-On: I3c08945f24343288c2c5614ab4b472d68a1e1d47

Change-Id: I7dd19a0c1ce948474bb3069073b3608ce265beb4
This commit is contained in:
Sripriya 2016-02-25 14:04:47 -08:00
parent 905c4ade56
commit 0fdd5a4717
40 changed files with 1598 additions and 266 deletions

View File

@ -388,4 +388,18 @@ function tacker_create_initial_network {
sudo ifconfig ${BR_MGMT} inet ${NETWORK_GATEWAY_MGMT_IP}
}
function tacker_register_default_vim {
local default_vim_id
DEFAULT_VIM_PROJECT_NAME="nfv"
DEFAULT_VIM_USER="nfv_user"
DEFAULT_VIM_PASSWORD="devstack"
DEFAULT_VIM_NAME="VIM0"
get_or_create_project $DEFAULT_VIM_PROJECT_NAME
get_or_create_user $DEFAULT_VIM_USER $DEFAULT_VIM_PASSWORD
get_or_add_user_project_role "admin" $DEFAULT_VIM_USER $DEFAULT_VIM_PROJECT_NAME
get_or_add_user_project_role "advsvc" $DEFAULT_VIM_USER $DEFAULT_VIM_PROJECT_NAME
VIM_CONFIG_FILE="$TACKER_DIR/devstack/samples/vim_config.yaml"
default_vim_id=$(tacker vim-register --name $DEFAULT_VIM_NAME --config-file $VIM_CONFIG_FILE -f value -c id)
echo $default_vim_id
iniset $TACKER_CONF nfvo_vim default_vim $DEFAULT_VIM_NAME
}

View File

@ -34,7 +34,8 @@ if is_service_enabled tacker; then
tacker_create_initial_network
echo_summary "Upload OpenWrt image"
tacker_create_openwrt_image
echo_summary "Registering default VIM"
tacker_register_default_vim
fi
if [[ "$1" == "unstack" ]]; then

View File

@ -0,0 +1,4 @@
auth_url: 'http://localhost:5000'
username: 'nfv_user'
password: 'devstack'
project_name: 'nfv'

View File

@ -1,136 +1,10 @@
{
"context_is_admin": "role:admin",
"admin_or_owner": "rule:context_is_admin or tenant_id:%(tenant_id)s",
"admin_or_network_owner": "rule:context_is_admin or tenant_id:%(network:tenant_id)s",
"admin_only": "rule:context_is_admin",
"regular_user": "",
"shared": "field:networks:shared=True",
"shared_firewalls": "field:firewalls:shared=True",
"external": "field:networks:router:external=True",
"shared": "field:vims:shared=True",
"default": "rule:admin_or_owner",
"subnets:private:read": "rule:admin_or_owner",
"subnets:private:write": "rule:admin_or_owner",
"subnets:shared:read": "rule:regular_user",
"subnets:shared:write": "rule:admin_only",
"create_subnet": "rule:admin_or_network_owner",
"get_subnet": "rule:admin_or_owner or rule:shared",
"update_subnet": "rule:admin_or_network_owner",
"delete_subnet": "rule:admin_or_network_owner",
"create_network": "",
"get_network": "rule:admin_or_owner or rule:shared or rule:external",
"get_network:router:external": "rule:regular_user",
"get_network:segments": "rule:admin_only",
"get_network:provider:network_type": "rule:admin_only",
"get_network:provider:physical_network": "rule:admin_only",
"get_network:provider:segmentation_id": "rule:admin_only",
"get_network:queue_id": "rule:admin_only",
"create_network:shared": "rule:admin_only",
"create_network:router:external": "rule:admin_only",
"create_network:segments": "rule:admin_only",
"create_network:provider:network_type": "rule:admin_only",
"create_network:provider:physical_network": "rule:admin_only",
"create_network:provider:segmentation_id": "rule:admin_only",
"update_network": "rule:admin_or_owner",
"update_network:segments": "rule:admin_only",
"update_network:shared": "rule:admin_only",
"update_network:provider:network_type": "rule:admin_only",
"update_network:provider:physical_network": "rule:admin_only",
"update_network:provider:segmentation_id": "rule:admin_only",
"delete_network": "rule:admin_or_owner",
"create_port": "",
"create_port:mac_address": "rule:admin_or_network_owner",
"create_port:fixed_ips": "rule:admin_or_network_owner",
"create_port:port_security_enabled": "rule:admin_or_network_owner",
"create_port:binding:host_id": "rule:admin_only",
"create_port:binding:profile": "rule:admin_only",
"create_port:mac_learning_enabled": "rule:admin_or_network_owner",
"get_port": "rule:admin_or_owner",
"get_port:queue_id": "rule:admin_only",
"get_port:binding:vif_type": "rule:admin_only",
"get_port:binding:vif_details": "rule:admin_only",
"get_port:binding:host_id": "rule:admin_only",
"get_port:binding:profile": "rule:admin_only",
"update_port": "rule:admin_or_owner",
"update_port:fixed_ips": "rule:admin_or_network_owner",
"update_port:port_security_enabled": "rule:admin_or_network_owner",
"update_port:binding:host_id": "rule:admin_only",
"update_port:binding:profile": "rule:admin_only",
"update_port:mac_learning_enabled": "rule:admin_or_network_owner",
"delete_port": "rule:admin_or_owner",
"create_router:external_gateway_info:enable_snat": "rule:admin_only",
"update_router:external_gateway_info:enable_snat": "rule:admin_only",
"create_firewall": "",
"get_firewall": "rule:admin_or_owner",
"create_firewall:shared": "rule:admin_only",
"get_firewall:shared": "rule:admin_only",
"update_firewall": "rule:admin_or_owner",
"update_firewall:shared": "rule:admin_only",
"delete_firewall": "rule:admin_or_owner",
"create_firewall_policy": "",
"get_firewall_policy": "rule:admin_or_owner or rule:shared_firewalls",
"create_firewall_policy:shared": "rule:admin_or_owner",
"update_firewall_policy": "rule:admin_or_owner",
"delete_firewall_policy": "rule:admin_or_owner",
"create_firewall_rule": "",
"get_firewall_rule": "rule:admin_or_owner or rule:shared_firewalls",
"update_firewall_rule": "rule:admin_or_owner",
"delete_firewall_rule": "rule:admin_or_owner",
"create_qos_queue": "rule:admin_only",
"get_qos_queue": "rule:admin_only",
"update_agent": "rule:admin_only",
"delete_agent": "rule:admin_only",
"get_agent": "rule:admin_only",
"create_dhcp-network": "rule:admin_only",
"delete_dhcp-network": "rule:admin_only",
"get_dhcp-networks": "rule:admin_only",
"create_l3-router": "rule:admin_only",
"delete_l3-router": "rule:admin_only",
"get_l3-routers": "rule:admin_only",
"get_dhcp-agents": "rule:admin_only",
"get_l3-agents": "rule:admin_only",
"get_loadbalancer-agent": "rule:admin_only",
"get_loadbalancer-pools": "rule:admin_only",
"create_router": "rule:regular_user",
"get_router": "rule:admin_or_owner",
"update_router:add_router_interface": "rule:admin_or_owner",
"update_router:remove_router_interface": "rule:admin_or_owner",
"delete_router": "rule:admin_or_owner",
"create_floatingip": "rule:regular_user",
"update_floatingip": "rule:admin_or_owner",
"delete_floatingip": "rule:admin_or_owner",
"get_floatingip": "rule:admin_or_owner",
"create_network_profile": "rule:admin_only",
"update_network_profile": "rule:admin_only",
"delete_network_profile": "rule:admin_only",
"get_network_profiles": "",
"get_network_profile": "",
"update_policy_profiles": "rule:admin_only",
"get_policy_profiles": "",
"get_policy_profile": "",
"create_metering_label": "rule:admin_only",
"delete_metering_label": "rule:admin_only",
"get_metering_label": "rule:admin_only",
"create_metering_label_rule": "rule:admin_only",
"delete_metering_label_rule": "rule:admin_only",
"get_metering_label_rule": "rule:admin_only",
"get_service_provider": "rule:regular_user",
"get_lsn": "rule:admin_only",
"create_lsn": "rule:admin_only"
"get_vim": "rule:admin_or_owner or rule:shared"
}

View File

@ -61,7 +61,8 @@ lock_path = $state_path/lock
#
# service_plugins =
# Example: service_plugins = router,firewall,lbaas,vpnaas,metering
service_plugins = tacker.vm.plugin.VNFMPlugin
service_plugins = vnfm,nfvo
# Paste configuration file
# api_paste_config = api-paste.ini
@ -395,6 +396,15 @@ auth_uri = http://127.0.0.1:5000
# Specify drivers for monitoring
# monitor_driver = ping, http_ping
[nfvo_vim]
# Supported VIM drivers, resource orchestration controllers such as OpenStack, kvm
#Default VIM driver is OpenStack
#vim_drivers = openstack
#Default VIM placement if vim id is not provided
default_vim = VIM0
[vim_keys]
#openstack = /etc/tacker/vim/fernet_keys
[tacker_nova]
# parameters for novaclient to talk to nova
region_name = RegionOne

View File

@ -30,3 +30,4 @@ oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
tosca-parser>=0.4.0 # Apache-2.0
heat-translator>=0.4.0 # Apache-2.0
cryptography>=1.0 # BSD/Apache-2.0

View File

@ -42,6 +42,9 @@ console_scripts =
tacker.service_plugins =
dummy = tacker.tests.unit.dummy_plugin:DummyServicePlugin
vnfm = tacker.vm.plugin:VNFMPlugin
nfvo = tacker.nfvo.nfvo_plugin:NfvoPlugin
tacker.nfvo.vim.drivers =
openstack = tacker.nfvo.drivers.vim.openstack_driver:OpenStack_Driver
tacker.openstack.common.cache.backends =
memory = tacker.openstack.common.cache._backends.memory:MemoryBackend
tacker.tacker.device.drivers =

View File

@ -11,48 +11,33 @@
# under the License.
from heatclient import client as heatclient
from keystoneclient.v2_0 import client as ks_client
from oslo_config import cfg
CONF = cfg.CONF
OPTS = [
cfg.StrOpt('heat_uri',
default='http://localhost:8004/v1',
help=_("Heat service URI to create VNF resources"
"specified in the VNFD templates")),
]
CONF.register_opts(OPTS, group='tacker_heat')
from tacker.vm import keystone
class OpenstackClients(object):
def __init__(self):
def __init__(self, auth_attr, region_name=None):
super(OpenstackClients, self).__init__()
self.keystone_client = None
self.keystone_plugin = keystone.Keystone()
self.heat_client = None
self.nova_client = None
self.auth_url = CONF.keystone_authtoken.auth_uri + '/v2.0'
self.auth_username = CONF.keystone_authtoken.username
self.auth_password = CONF.keystone_authtoken.password
self.auth_tenant_name = CONF.keystone_authtoken.project_name
self.keystone_client = None
self.region_name = region_name
self.auth_attr = auth_attr
def _keystone_client(self):
return ks_client.Client(
tenant_name=self.auth_tenant_name,
username=self.auth_username,
password=self.auth_password,
auth_url=self.auth_url)
version = self.auth_attr['auth_url'].rpartition('/')[2]
return self.keystone_plugin.initialize_client(version,
**self.auth_attr)
def _heat_client(self):
tenant_id = self.auth_token['tenant_id']
token = self.auth_token['id']
endpoint = CONF.tacker_heat.heat_uri + '/' + tenant_id
return heatclient.Client('1', endpoint=endpoint, token=token)
endpoint = self.keystone_session.get_endpoint(
service_type='orchestration', region_name=self.region_name)
return heatclient.Client('1', endpoint=endpoint,
session=self.keystone_session)
@property
def auth_token(self):
return self.keystone.service_catalog.get_token()
def keystone_session(self):
return self.keystone.session
@property
def keystone(self):

View File

@ -27,13 +27,17 @@ import os
import random
import signal
import socket
import sys
import uuid
from eventlet.green import subprocess
import netaddr
from oslo_config import cfg
from oslo_utils import importutils
from stevedore import driver
from tacker.common import constants as q_const
from tacker.i18n import _LE
from tacker.openstack.common import lockutils
from tacker.openstack.common import log as logging
@ -63,7 +67,7 @@ MEM_UNITS = {
}
}
}
CONF = cfg.CONF
synchronized = lockutils.synchronized_with_prefix(SYNCHRONIZED_PREFIX)
@ -347,3 +351,44 @@ def change_memory_unit(mem, to):
return eval(mem_arr[0] +
MEM_UNITS[unit][to]["op"] +
MEM_UNITS[unit][to]["val"])
def load_class_by_alias_or_classname(namespace, name):
"""Load class using stevedore alias or the class name
Load class using the stevedore driver manager
:param namespace: namespace where the alias is defined
:param name: alias or class name of the class to be loaded
:returns class if calls can be loaded
:raises ImportError if class cannot be loaded
"""
if not name:
LOG.error(_LE("Alias or class name is not set"))
raise ImportError(_("Class not found."))
try:
# Try to resolve class by alias
mgr = driver.DriverManager(namespace, name)
class_to_load = mgr.driver
except RuntimeError:
e1_info = sys.exc_info()
# Fallback to class name
try:
class_to_load = importutils.import_class(name)
except (ImportError, ValueError):
LOG.error(_LE("Error loading class by alias"),
exc_info=e1_info)
LOG.error(_LE("Error loading class by class name"),
exc_info=True)
raise ImportError(_("Class not found."))
return class_to_load
def deep_update(orig_dict, new_dict):
for key, value in new_dict.items():
if isinstance(value, dict):
if key in orig_dict and isinstance(orig_dict[key], dict):
deep_update(orig_dict[key], value)
continue
orig_dict[key] = value

View File

@ -0,0 +1,68 @@
# Copyright 2016 OpenStack Foundation
#
# 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.
#
"""multisite_vim
Revision ID: 5246a6bd410f
Revises: 24bec5f211c7
Create Date: 2016-03-22 14:05:15.129330
"""
# revision identifiers, used by Alembic.
revision = '5246a6bd410f'
down_revision = '24bec5f211c7'
from alembic import op
import sqlalchemy as sa
def upgrade(active_plugins=None, options=None):
op.create_table('vims',
sa.Column('id', sa.String(length=255), nullable=False),
sa.Column('type', sa.String(length=255), nullable=False),
sa.Column('tenant_id', sa.String(length=255), nullable=True),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('placement_attr', sa.PickleType(), nullable=True),
sa.Column('shared', sa.Boolean(), server_default=sa.text(u'true'),
nullable=False),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table('vimauths',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('vim_id', sa.String(length=255), nullable=False),
sa.Column('password', sa.String(length=128), nullable=False),
sa.Column('auth_url', sa.String(length=255), nullable=False),
sa.Column('vim_project', sa.PickleType(), nullable=False),
sa.Column('auth_cred', sa.PickleType(), nullable=False),
sa.ForeignKeyConstraint(['vim_id'], ['vims.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('auth_url')
)
op.add_column(u'devices', sa.Column('placement_attr', sa.PickleType(),
nullable=True))
op.add_column(u'devices', sa.Column('vim_id', sa.String(length=36),
nullable=False))
op.create_foreign_key(None, 'devices', 'vims', ['vim_id'], ['id'])
def downgrade(active_plugins=None, options=None):
op.drop_constraint(None, 'devices', type_='foreignkey')
op.drop_column(u'devices', 'vim_id')
op.drop_column(u'devices', 'placement_attr')
op.drop_table('vimauths')
op.drop_table('vims')

View File

@ -1 +1 @@
24bec5f211c7
5246a6bd410f

View File

@ -22,6 +22,7 @@ Based on this comparison database can be healed with healing migration.
"""
from tacker.db import model_base
from tacker.db.nfvo import nfvo_db # noqa
from tacker.db.vm import proxy_db # noqa
from tacker.db.vm import vm_db # noqa

View File

169
tacker/db/nfvo/nfvo_db.py Normal file
View File

@ -0,0 +1,169 @@
# 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 uuid
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc as orm_exc
from sqlalchemy import sql
from tacker.db import api as tdbapi
from tacker.db import db_base
from tacker.db import model_base
from tacker.db import models_v1
from tacker.db.vm import vm_db
from tacker.extensions import nfvo
from tacker import manager
from tacker.openstack.common.db import exception
from tacker.openstack.common import uuidutils
VIM_ATTRIBUTES = ('id', 'type', 'tenant_id', 'name', 'description',
'placement_attr', 'shared')
VIM_AUTH_ATTRIBUTES = ('auth_url', 'vim_project', 'password', 'auth_cred')
class Vim(model_base.BASE, models_v1.HasTenant):
id = sa.Column(sa.String(255),
primary_key=True,
default=uuidutils.generate_uuid)
type = sa.Column(sa.String(255), nullable=False)
tenant_id = sa.Column(sa.String(255), nullable=True)
name = sa.Column(sa.String(255), nullable=True)
description = sa.Column(sa.String(255), nullable=True)
placement_attr = sa.Column(sa.PickleType, nullable=True)
shared = sa.Column(sa.Boolean, default=True, server_default=sql.true(
), nullable=False)
vim_auth = orm.relationship('VimAuth')
class VimAuth(model_base.BASE, models_v1.HasId):
vim_id = sa.Column(sa.String(255), sa.ForeignKey('vims.id'),
nullable=False)
password = sa.Column(sa.String(128), nullable=False)
auth_url = sa.Column(sa.String(255), nullable=False)
vim_project = sa.Column(sa.PickleType, nullable=False)
auth_cred = sa.Column(sa.PickleType, nullable=False)
__table_args__ = (sa.UniqueConstraint('auth_url'), {})
class NfvoPluginDb(nfvo.NFVOPluginBase, db_base.CommonDbMixin):
def __init__(self):
tdbapi.register_models()
super(NfvoPluginDb, self).__init__()
@property
def _core_plugin(self):
return manager.TackerManager.get_plugin()
def _make_vim_dict(self, vim_db, fields=None):
res = dict((key, vim_db[key]) for key in VIM_ATTRIBUTES)
vim_auth_db = vim_db.vim_auth
res['auth_url'] = vim_auth_db[0].auth_url
res['vim_project'] = vim_auth_db[0].vim_project
res['auth_cred'] = vim_auth_db[0].auth_cred
res['auth_cred']['password'] = vim_auth_db[0].password
return self._fields(res, fields)
def _fields(self, resource, fields):
if fields:
return dict(((key, item) for key, item in resource.items()
if key in fields))
return resource
def _get_resource(self, context, model, id):
try:
return self._get_by_id(context, model, id)
except orm_exc.NoResultFound:
if issubclass(model, Vim):
raise nfvo.VimNotFoundException(vim_id=id)
else:
raise
def create_vim(self, context, vim):
vim_cred = vim['auth_cred']
try:
with context.session.begin(subtransactions=True):
vim_db = Vim(
id=vim.get('id'),
type=vim.get('type'),
tenant_id=vim.get('tenant_id'),
name=vim.get('name'),
description=vim.get('description'),
placement_attr=vim.get('placement_attr'))
context.session.add(vim_db)
vim_auth_db = VimAuth(
id=str(uuid.uuid4()),
vim_id=vim.get('id'),
password=vim_cred.pop('password'),
vim_project=vim.get('vim_project'),
auth_url=vim.get('auth_url'),
auth_cred=vim_cred)
context.session.add(vim_auth_db)
except exception.DBDuplicateEntry:
raise nfvo.VimDuplicateUrlException()
return self._make_vim_dict(vim_db)
def delete_vim(self, context, vim_id):
with context.session.begin(subtransactions=True):
vim_db = self._get_resource(context, Vim, vim_id)
context.session.query(VimAuth).filter_by(
vim_id=vim_id).delete()
context.session.delete(vim_db)
def is_vim_still_in_use(self, context, vim_id):
with context.session.begin(subtransactions=True):
devices_db = context.session.query(vm_db.Device).filter_by(
vim_id=vim_id).first()
if devices_db is not None:
raise nfvo.VimInUseException(vim_id=vim_id)
return devices_db
def get_vim(self, context, vim_id, fields=None):
vim_db = self._get_resource(context, Vim, vim_id)
return self._make_vim_dict(vim_db)
def get_vims(self, context, filters=None, fields=None):
return self._get_collection(context, Vim, self._make_vim_dict,
filters=filters, fields=fields)
def update_vim(self, context, vim_id, vim):
with context.session.begin(subtransactions=True):
vim_cred = vim['auth_cred']
vim_project = vim['vim_project']
try:
vim_auth_db = (self._model_query(context, VimAuth).filter(
VimAuth.vim_id == vim_id).with_lockmode('update').one())
except orm_exc.NoResultFound:
raise nfvo.VimNotFound(vim_id=vim_id)
vim_auth_db.update({'auth_cred': vim_cred, 'password':
vim_cred.pop('password'), 'vim_project':
vim_project})
return self.get_vim(context, vim_id)
def get_vim_by_name(self, context, vim_name, fields=None):
vim_db = self._get_by_name(context, Vim, vim_name)
return self._make_vim_dict(vim_db)
def _get_by_name(self, context, model, name):
try:
query = self._model_query(context, model)
return query.filter(model.name == name).one()
except orm_exc.NoResultFound:
if issubclass(model, Vim):
raise

View File

@ -120,6 +120,9 @@ class Device(model_base.BASE, models_v1.HasTenant):
attributes = orm.relationship("DeviceAttribute", backref="device")
status = sa.Column(sa.String(255), nullable=False)
vim_id = sa.Column(sa.String(36), sa.ForeignKey('vims.id'), nullable=False)
placement_attr = sa.Column(sa.PickleType, nullable=True)
vim = orm.relationship('Vim')
class DeviceAttribute(model_base.BASE, models_v1.HasId):
@ -195,7 +198,8 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
'attributes': self._make_dev_attrs_dict(device_db.attributes),
}
key_list = ('id', 'tenant_id', 'name', 'description', 'instance_id',
'template_id', 'status', 'mgmt_url')
'vim_id', 'placement_attr', 'template_id', 'status',
'mgmt_url')
res.update((key, device_db[key]) for key in key_list)
return self._fields(res, fields)
@ -335,13 +339,14 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
# called internally, not by REST API
def _create_device_pre(self, context, device):
device = device['device']
LOG.debug(_('device %s'), device)
tenant_id = self._get_tenant_id_for_create(context, device)
template_id = device['template_id']
name = device.get('name')
device_id = device.get('id') or str(uuid.uuid4())
attributes = device.get('attributes', {})
vim_id = device.get('vim_id')
placement_attr = device.get('placement_attr', {})
with context.session.begin(subtransactions=True):
template_db = self._get_resource(context, DeviceTemplate,
template_id)
@ -351,13 +356,15 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
description=template_db.description,
instance_id=None,
template_id=template_id,
vim_id=vim_id,
placement_attr=placement_attr,
status=constants.PENDING_CREATE)
context.session.add(device_db)
for key, value in attributes.items():
arg = DeviceAttribute(
id=str(uuid.uuid4()), device_id=device_id,
key=key, value=value)
context.session.add(arg)
arg = DeviceAttribute(
id=str(uuid.uuid4()), device_id=device_id,
key=key, value=value)
context.session.add(arg)
return self._make_device_dict(device_db)
@ -376,8 +383,10 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
query.update({'status': constants.ERROR})
for (key, value) in device_dict['attributes'].items():
self._device_attribute_update_or_create(context, device_id,
key, value)
# do not store decrypted vim auth in device attr table
if 'vim_auth' not in key:
self._device_attribute_update_or_create(context, device_id,
key, value)
def _create_device_status(self, context, device_id, new_status):
with context.session.begin(subtransactions=True):
@ -421,7 +430,8 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
delete(synchronize_session='fetch'))
for (key, value) in dev_attrs.items():
self._device_attribute_update_or_create(context, device_id,
if 'vim_auth' not in key:
self._device_attribute_update_or_create(context, device_id,
key, value)
def _delete_device_pre(self, context, device_id):
@ -532,7 +542,9 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
description=device_db.description,
instance_id=device_db.instance_id,
mgmt_url=device_db.mgmt_url,
status=device_db.status)
status=device_db.status,
vim_id=device_db.vim_id,
placement_attr=device_db.placement_attr)
context.session.add(new_device_db)
(self._model_query(context, DeviceAttribute).

207
tacker/extensions/nfvo.py Normal file
View File

@ -0,0 +1,207 @@
# Copyright 2016 Brocade Communications Systems 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 abc
import six
from tacker.api import extensions
from tacker.api.v1 import attributes as attr
from tacker.api.v1 import resource_helper
from tacker.common import exceptions
from tacker.plugins.common import constants
from tacker.services import service_base
class VimUnauthorizedException(exceptions.TackerException):
message = _("%(message)s")
class VimConnectionException(exceptions.TackerException):
message = _("%(message)s")
class VimInUseException(exceptions.TackerException):
message = _("VIM %(vim_id)s is still in use by VNF")
class VimDefaultIdException(exceptions.TackerException):
message = _("Default VIM name %(vim_name)s is invalid or there are "
"multiple VIM matches found. Please specify a valid default "
"VIM in tacker.conf")
class VimNotFoundException(exceptions.TackerException):
message = _("Specified VIM id %(vim_id)s is invalid. Please verify and "
"pass a valid VIM id")
class VimRegionNotFoundException(exceptions.TackerException):
message = _("Unknown VIM region name %(region_name)s")
class VimKeyNotFoundException(exceptions.TackerException):
message = _("Unable to find key file for VIM %(vim_id)s")
class VimDuplicateUrlException(exceptions.TackerException):
message = _("VIM with specified auth URL already exists. Cannot register "
"duplicate VIM")
RESOURCE_ATTRIBUTE_MAP = {
'vims': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True,
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True
},
'type': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True
},
'auth_url': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True
},
'auth_cred': {
'allow_post': True,
'allow_put': True,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
},
'vim_project': {
'allow_post': True,
'allow_put': True,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'placement_attr': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
'default': None,
},
'shared': {
'allow_post': False,
'allow_put': False,
'is_visible': False,
'convert_to': attr.convert_to_boolean,
'required_by_policy': True
},
}
}
class Nfvo(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return 'NFVO'
@classmethod
def get_alias(cls):
return 'NFV Orchestrator'
@classmethod
def get_description(cls):
return "Extension for NFV Orchestrator"
@classmethod
def get_namespace(cls):
return 'http://wiki.openstack.org/Tacker'
@classmethod
def get_updated(cls):
return "2015-12-21T10:00:00-00:00"
@classmethod
def get_resources(cls):
special_mappings = {}
plural_mappings = resource_helper.build_plural_mappings(
special_mappings, RESOURCE_ATTRIBUTE_MAP)
attr.PLURALS.update(plural_mappings)
return resource_helper.build_resource_info(
plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.NFVO,
translate_name=True)
@classmethod
def get_plugin_interface(cls):
return NFVOPluginBase
def update_attributes_map(self, attributes):
super(Nfvo, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
version_map = {'1.0': RESOURCE_ATTRIBUTE_MAP}
return version_map.get(version, {})
@six.add_metaclass(abc.ABCMeta)
class NFVOPluginBase(service_base.NFVPluginBase):
def get_plugin_name(self):
return constants.NFVO
def get_plugin_type(self):
return constants.NFVO
def get_plugin_description(self):
return 'Tacker NFV Orchestrator plugin'
@abc.abstractmethod
def create_vim(self, context, vim):
pass
@abc.abstractmethod
def delete_vim(self, context, vim_id):
pass
@abc.abstractmethod
def get_vim(self, context, vim_id, fields=None):
pass
@abc.abstractmethod
def get_vims(self, context, filters=None, fields=None):
pass
def get_vim_by_name(self, context, vim_name, fields=None):
raise NotImplementedError()

View File

@ -23,7 +23,7 @@ from tacker.api.v1 import resource_helper
from tacker.common import exceptions
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
from tacker.services.service_base import NFVPluginBase
from tacker.services import service_base
LOG = logging.getLogger(__name__)
@ -225,6 +225,13 @@ RESOURCE_ATTRIBUTE_MAP = {
'validate': {'type:uuid': None},
'is_visible': True,
},
'vim_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'name': {
'allow_post': True,
'allow_put': True,
@ -258,6 +265,13 @@ RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True,
'default': {},
},
'placement_attr': {
'allow_post': True,
'allow_put': False,
'validate': {'type:dict_or_none': None},
'is_visible': True,
'default': {},
},
'status': {
'allow_post': False,
'allow_put': False,
@ -313,7 +327,7 @@ class Vnfm(extensions.ExtensionDescriptor):
@six.add_metaclass(abc.ABCMeta)
class VNFMPluginBase(NFVPluginBase):
class VNFMPluginBase(service_base.NFVPluginBase):
def get_plugin_name(self):
return constants.VNFM

View File

@ -17,7 +17,6 @@ from oslo_config import cfg
from tacker.common import rpc_compat
from tacker.common import utils
from tacker.openstack.common import importutils
from tacker.openstack.common import log as logging
from tacker.openstack.common import periodic_task
@ -101,6 +100,27 @@ class TackerManager(object):
self.service_plugins = {}
self._load_service_plugins()
@staticmethod
def load_class_for_provider(namespace, plugin_provider):
"""Loads plugin using alias or class name
Load class using stevedore alias or the class name
:param namespace: namespace where alias is defined
:param plugin_provider: plugin alias or class name
:returns plugin that is loaded
:raises ImportError if fails to load plugin
"""
try:
return utils.load_class_by_alias_or_classname(namespace,
plugin_provider)
except ImportError:
raise ImportError(_("Plugin '%s' not found.") % plugin_provider)
def _get_plugin_instance(self, namespace, plugin_provider):
plugin_class = self.load_class_for_provider(namespace, plugin_provider)
return plugin_class()
def _load_service_plugins(self):
"""Loads service plugins.
@ -112,14 +132,10 @@ class TackerManager(object):
for provider in plugin_providers:
if provider == '':
continue
try:
LOG.info(_("Loading Plugin: %s"), provider)
plugin_class = importutils.import_class(provider)
except ImportError:
LOG.exception(_("Error loading plugin"))
raise ImportError(_("Plugin not found."))
plugin_inst = plugin_class()
LOG.info(_("Loading Plugin: %s"), provider)
plugin_inst = self._get_plugin_instance('tacker.service_plugins',
provider)
# only one implementation of svc_type allowed
# specifying more than one plugin
# for the same type is a fatal exception
@ -129,7 +145,6 @@ class TackerManager(object):
plugin_inst.get_plugin_type())
self.service_plugins[plugin_inst.get_plugin_type()] = plugin_inst
# # search for possible agent notifiers declared in service plugin
# # (needed by agent management extension)
# if (hasattr(self.plugin, 'agent_notifiers') and

0
tacker/nfvo/__init__.py Normal file
View File

View File

View File

View File

@ -0,0 +1,84 @@
# 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 abc
import six
from tacker.api import extensions
@six.add_metaclass(abc.ABCMeta)
class VimAbstractDriver(extensions.PluginInterface):
@abc.abstractmethod
def get_type(self):
"""Get VIM Driver type
Return one of predefined types of VIMs.
"""
pass
@abc.abstractmethod
def get_name(self):
"""Get VIM name
Return a symbolic name for the VIM driver.
"""
pass
@abc.abstractmethod
def get_description(self):
pass
@abc.abstractmethod
def register_vim(self, context, vim_obj):
"""Register VIM object in to NFVO plugin
Validate, encode and store VIM information for deploying VNFs.
"""
pass
@abc.abstractmethod
def deregister_vim(self, context, vim_id):
"""Deregister VIM object from NFVO plugin
Cleanup VIM data and delete VIM information
"""
pass
@abc.abstractmethod
def authenticate_vim(self, context, vim_obj):
"""Authenticate VIM connection parameters
Validate authentication credentials and connectivity of VIM
"""
pass
@abc.abstractmethod
def encode_vim_auth(self, context, vim_id, auth):
"""Encrypt VIM credentials
Encrypt and store VIM sensitive information such as password
"""
pass
@abc.abstractmethod
def delete_vim_auth(self, vim_id):
"""Delete VIM auth keys
Delete VIM sensitive information such as keys from file system or DB
"""
pass

View File

@ -0,0 +1,180 @@
# 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 oslo_config import cfg
from tacker.common import log
from tacker.extensions import nfvo
from tacker.nfvo.drivers.vim import abstract_vim_driver
from tacker.openstack.common import log as logging
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.')]
cfg.CONF.register_opts(OPTS, 'vim_keys')
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', None)
auth_cred['project_name'] = vim_project.get('name', None)
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.pop('id', None)
auth_cred['tenant_name'] = vim_project.pop('name', None)
# 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):
try:
ks_client = self.keystone.initialize_client(version=version,
**auth)
except Exception as e:
LOG.error(_('VIM authentication failed'))
raise nfvo.VimUnauthorizedException(message=e.message)
return ks_client
def _find_regions(self, ks_client):
if ks_client.version == 'v2.0':
service_list = ks_client.services.list()
heat_service_id = (service.id for service in
service_list if service.type == 'orchestration')
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]
if not region_list:
LOG.info(_('Unable to find VIM regions'))
return
return region_list
def discover_placement_attr(self, vim_obj, ks_client):
"""Fetch VIM placement information
Attributes can include regions, AZ.
"""
regions_list = self._find_regions(ks_client)
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 complete %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)

View File

@ -0,0 +1,91 @@
# 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 uuid
from oslo_config import cfg
from tacker.common import driver_manager
from tacker.common import log
from tacker.common import utils
from tacker.db.nfvo import nfvo_db
from tacker.openstack.common import excutils
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class NfvoPlugin(nfvo_db.NfvoPluginDb):
"""NFVO reference plugin for NFVO extension
Implements the NFVO extension and defines public facing APIs for VIM
operations. NFVO internally invokes the appropriate VIM driver in
backend based on configured VIM types. Plugin also interacts with VNFM
extension for providing the specified VIM information
"""
supported_extension_aliases = ['nfvo']
OPTS = [
cfg.ListOpt(
'vim_drivers', default=['openstack'],
help=_('VIM driver for launching VNFs')),
]
cfg.CONF.register_opts(OPTS, 'nfvo_vim')
def __init__(self):
super(NfvoPlugin, self).__init__()
self._vim_drivers = driver_manager.DriverManager(
'tacker.nfvo.vim.drivers',
cfg.CONF.nfvo_vim.vim_drivers)
@log.log
def create_vim(self, context, vim):
LOG.debug(_('Create vim called with parameters %s'), vim)
vim_obj = vim['vim']
vim_type = vim_obj['type']
vim_obj['id'] = str(uuid.uuid4())
try:
self._vim_drivers.invoke(vim_type, 'register_vim', vim_obj=vim_obj)
res = super(NfvoPlugin, self).create_vim(context, vim_obj)
return res
except Exception:
with excutils.save_and_reraise_exception():
self._vim_drivers.invoke(vim_type, 'delete_vim_auth',
vim_id=vim_obj['id'])
def _get_vim(self, context, vim_id):
if not self.is_vim_still_in_use(context, vim_id):
return self.get_vim(context, vim_id)
@log.log
def update_vim(self, context, vim_id, vim):
vim_obj = self._get_vim(context, vim_id)
utils.deep_update(vim_obj, vim['vim'])
vim_type = vim_obj['type']
try:
self._vim_drivers.invoke(vim_type, 'register_vim', vim_obj=vim_obj)
return super(NfvoPlugin, self).update_vim(context, vim_id, vim_obj)
except Exception:
with excutils.save_and_reraise_exception():
self._vim_drivers.invoke(vim_type, 'delete_vim_auth',
vim_id=vim_obj['id'])
@log.log
def delete_vim(self, context, vim_id):
vim_obj = self._get_vim(context, vim_id)
self._vim_drivers.invoke(vim_obj['type'], 'deregister_vim',
vim_id=vim_id)
super(NfvoPlugin, self).delete_vim(context, vim_id)

View File

@ -19,11 +19,13 @@
CORE = "CORE"
DUMMY = "DUMMY"
VNFM = "VNFM"
NFVO = "NFVO"
COMMON_PREFIXES = {
CORE: "",
DUMMY: "/dummy_svc",
VNFM: "",
NFVO: ""
}
# Service operation status constants

33
tacker/tests/unit/base.py Normal file
View File

@ -0,0 +1,33 @@
# 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 mock
from oslo_config import cfg
from oslo_config import fixture as config_fixture
from oslotest import base
CONF = cfg.CONF
class TestCase(base.BaseTestCase):
def setUp(self):
super(TestCase, self).setUp()
self.config_fixture = self.useFixture(config_fixture.Config(CONF))
def _mock(self, target, new=mock.DEFAULT):
patcher = mock.patch(target, new)
return patcher.start()

View File

@ -14,10 +14,10 @@
# under the License.
import fixtures
import testtools
from tacker.db import api as db_api
from tacker.db import model_base
from tacker.tests.unit import base
class SqlFixture(fixtures.Fixture):
@ -42,7 +42,7 @@ class SqlFixture(fixtures.Fixture):
self.addCleanup(clear_tables)
class SqlTestCase(testtools.TestCase):
class SqlTestCase(base.TestCase):
def setUp(self):
super(SqlTestCase, self).setUp()

View File

@ -47,6 +47,7 @@ def get_dummy_vnfd_obj():
def get_dummy_vnf_obj():
return {'vnf': {'description': 'dummy_vnf_description',
'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_vnf',
'attributes': {}}}
@ -152,3 +153,10 @@ def get_dummy_device_obj_userdata_attr():
'services': [], 'attributes': {u'param_values': userdata_params},
'id': '18685f68-2b2a-4185-8566-74f54e548811',
'description': u"Parameterized VNF descriptor"}
def get_vim_auth_obj():
return {'username': 'test_user', 'password': 'test_password',
'project_id': None, 'project_name': 'test_project',
'auth_url': 'http://localhost:5000/v3', 'user_domain_id':
'default', 'project_domain_id': 'default'}

View File

@ -16,10 +16,10 @@
import codecs
import mock
import os
import testtools
import yaml
from tacker import context
from tacker.tests.unit import base
from tacker.tests.unit.db import utils
from tacker.vm.infra_drivers.heat import heat
@ -45,7 +45,7 @@ def _get_template(name):
return f.read()
class TestDeviceHeat(testtools.TestCase):
class TestDeviceHeat(base.TestCase):
hot_template = _get_template('hot_openwrt.yaml')
hot_param_template = _get_template('hot_openwrt_params.yaml')
hot_ipparam_template = _get_template('hot_openwrt_ipparams.yaml')
@ -142,7 +142,8 @@ class TestDeviceHeat(testtools.TestCase):
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
expected_fields = self._get_expected_fields()
result = self.heat_driver.create(plugin=None, context=self.context,
device=device_obj)
device=device_obj,
auth_attr=utils.get_vim_auth_obj())
self.heat_client.create.assert_called_once_with(expected_fields)
self.assertEqual(expected_result, result)
@ -151,7 +152,8 @@ class TestDeviceHeat(testtools.TestCase):
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
expected_fields = self._get_expected_fields_user_data()
result = self.heat_driver.create(plugin=None, context=self.context,
device=device_obj)
device=device_obj,
auth_attr=utils.get_vim_auth_obj())
self.heat_client.create.assert_called_once_with(expected_fields)
self.assertEqual(expected_result, result)
@ -160,7 +162,8 @@ class TestDeviceHeat(testtools.TestCase):
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
expected_fields = self._get_expected_fields_ipaddr_data()
result = self.heat_driver.create(plugin=None, context=self.context,
device=device_obj)
device=device_obj,
auth_attr=utils.get_vim_auth_obj())
self.heat_client.create.assert_called_once_with(expected_fields)
self.assertEqual(expected_result, result)
@ -171,13 +174,15 @@ class TestDeviceHeat(testtools.TestCase):
self.heat_driver.create_wait(plugin=None,
context=self.context,
device_dict=device_obj,
device_id=device_id)
device_id=device_id,
auth_attr=utils.get_vim_auth_obj())
self.assertEqual(device_obj, expected_result)
def test_delete(self):
device_id = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
self.heat_driver.delete(plugin=None, context=self.context,
device_id=device_id)
device_id=device_id,
auth_attr=utils.get_vim_auth_obj())
self.heat_client.delete.assert_called_once_with(device_id)
def test_update(self):
@ -185,9 +190,10 @@ class TestDeviceHeat(testtools.TestCase):
device_config_obj = utils.get_dummy_device_update_config_attr()
expected_device_update = self._get_expected_device_update_obj()
device_id = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
self.heat_driver.update(None, self.context,
device_id, device_obj,
device_config_obj)
self.heat_driver.update(plugin=None, context=self.context,
device_id=device_id, device_dict=device_obj,
device=device_config_obj,
auth_attr=utils.get_vim_auth_obj())
self.assertEqual(device_obj, expected_device_update)
def test_create_device_template_pre_tosca(self):
@ -238,10 +244,10 @@ class TestDeviceHeat(testtools.TestCase):
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
expected_fields = self._get_expected_fields_tosca(hot_tpl_name)
expected_device = self._get_expected_tosca_device(tosca_tpl_name,
hot_tpl_name)
hot_tpl_name)
result = self.heat_driver.create(plugin=None, context=self.context,
device=device)
# self.heat_client.create.assert_called_once_with(expected_fields)
device=device,
auth_attr=utils.get_vim_auth_obj())
actual_fields = self.heat_client.create.call_args[0][0]
actual_fields["template"] = yaml.safe_load(actual_fields["template"])
expected_fields["template"] = \
@ -249,6 +255,7 @@ class TestDeviceHeat(testtools.TestCase):
self.assertEqual(actual_fields, expected_fields)
device["attributes"]["heat_template"] = yaml.safe_load(
device["attributes"]["heat_template"])
self.heat_client.create.assert_called_once_with(expected_fields)
self.assertEqual(expected_result, result)
self.assertEqual(device, expected_device)

View File

View File

@ -0,0 +1,117 @@
# 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 mock
from oslo_config import cfg
from tacker.nfvo.drivers.vim import openstack_driver
from tacker.tests.unit import base
from tacker.tests.unit.db import utils
OPTS = [cfg.StrOpt('user_domain_id', default='default', help='User Domain Id'),
cfg.StrOpt('project_domain_id', default='default', help='Project '
'Domain Id')]
cfg.CONF.register_opts(OPTS, 'keystone_authtoken')
CONF = cfg.CONF
class FakeKeystone(mock.Mock):
pass
class mock_dict(dict):
def __getattr__(self, item):
return self.get(item)
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
class TestOpenstack_Driver(base.TestCase):
def setUp(self):
super(TestOpenstack_Driver, self).setUp()
self._mock_keystone()
self.keystone.create_key_dir.return_value = 'test_keys'
self.config_fixture.config(group='vim_keys', openstack='/tmp/')
self.openstack_driver = openstack_driver.OpenStack_Driver()
self.vim_obj = self.get_vim_obj()
self.auth_obj = utils.get_vim_auth_obj()
self.addCleanup(mock.patch.stopall)
def _mock_keystone(self):
self.keystone = mock.Mock(wraps=FakeKeystone())
fake_keystone = mock.Mock()
fake_keystone.return_value = self.keystone
self._mock(
'tacker.vm.keystone.Keystone', fake_keystone)
def get_vim_obj(self):
return {'id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff', 'type':
'openstack', 'auth_url': 'http://localhost:5000',
'auth_cred': {'username': 'test_user', 'password':
'test_password'}, 'name': 'VIM0',
'vim_project': {'name': 'test_project'}}
def test_register_keystone_v3(self):
regions = [mock_dict({'id': 'RegionOne'})]
attrs = {'regions.list.return_value': regions}
keystone_version = 'v3'
mock_ks_client = mock.Mock(version=keystone_version, **attrs)
self.keystone.get_version.return_value = keystone_version
self._test_register_vim(self.vim_obj, mock_ks_client)
mock_ks_client.regions.list.assert_called_once_with()
self.keystone.initialize_client.assert_called_once_with(
version=keystone_version, **self.auth_obj)
def test_register_keystone_v2(self):
services_list = [mock_dict({'type': 'orchestration', 'id':
'test_id'})]
endpoints_regions = mock_dict({'region': 'RegionOne'})
endpoints_list = [mock_dict({'service_id': 'test_id', 'regions':
endpoints_regions})]
attrs = {'endpoints.list.return_value': endpoints_list,
'services.list.return_value': services_list}
keystone_version = 'v2.0'
mock_ks_client = mock.Mock(version='v2.0', **attrs)
self.keystone.get_version.return_value = keystone_version
auth_obj = {'tenant_name': 'test_project', 'username': 'test_user',
'password': 'test_password', 'auth_url':
'http://localhost:5000/v2.0', 'tenant_id': None}
self._test_register_vim(self.vim_obj, mock_ks_client)
self.keystone.initialize_client.assert_called_once_with(
version=keystone_version, **auth_obj)
def _test_register_vim(self, vim_obj, mock_ks_client):
self.keystone.initialize_client.return_value = mock_ks_client
fernet_attrs = {'encrypt.return_value': 'encrypted_password'}
mock_fernet_obj = mock.Mock(**fernet_attrs)
mock_fernet_key = 'test_fernet_key'
self.keystone.create_fernet_key.return_value = (mock_fernet_key,
mock_fernet_obj)
file_mock = mock.mock_open()
with mock.patch('six.moves.builtins.open', file_mock, create=True):
self.openstack_driver.register_vim(vim_obj)
mock_fernet_obj.encrypt.assert_called_once_with(mock.ANY)
file_mock().write.assert_called_once_with('test_fernet_key')
@mock.patch('tacker.nfvo.drivers.vim.openstack_driver.os.remove')
@mock.patch('tacker.nfvo.drivers.vim.openstack_driver.os.path'
'.join')
def test_deregister_vim(self, mock_os_path, mock_os_remove):
vim_id = 'my_id'
file_path = CONF.vim_keys.openstack + '/' + vim_id
mock_os_path.return_value = file_path
self.openstack_driver.deregister_vim(vim_id)
mock_os_remove.assert_called_once_with(file_path)

View File

@ -0,0 +1,115 @@
# 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 uuid
import mock
from tacker import context
from tacker.db.nfvo import nfvo_db
from tacker.nfvo import nfvo_plugin
from tacker.tests.unit.db import base as db_base
class FakeDriverManager(mock.Mock):
def invoke(self, *args, **kwargs):
if 'create' in args:
return str(uuid.uuid4())
class TestNfvoPlugin(db_base.SqlTestCase):
def setUp(self):
super(TestNfvoPlugin, self).setUp()
self.addCleanup(mock.patch.stopall)
self.context = context.get_admin_context()
self._mock_driver_manager()
self.nfvo_plugin = nfvo_plugin.NfvoPlugin()
def _mock_driver_manager(self):
self._driver_manager = mock.Mock(wraps=FakeDriverManager())
self._driver_manager.__contains__ = mock.Mock(
return_value=True)
fake_driver_manager = mock.Mock()
fake_driver_manager.return_value = self._driver_manager
self._mock(
'tacker.common.driver_manager.DriverManager', fake_driver_manager)
def _insert_dummy_vim(self):
session = self.context.session
vim_db = nfvo_db.Vim(
id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='fake_vim',
description='fake_vim_description',
type='openstack',
placement_attr={'regions': ['RegionOne']})
vim_auth_db = nfvo_db.VimAuth(
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
password='encrypted_pw',
auth_url='http://localhost:5000',
vim_project={'name': 'test_project'},
auth_cred={'username': 'test_user', 'user_domain_id': 'default',
'project_domain_d': 'default'})
session.add(vim_db)
session.add(vim_auth_db)
session.flush()
def test_create_vim(self):
vim_dict = {'vim': {'type': 'openstack', 'auth_url':
'http://localhost:5000', 'vim_project': {'name':
'test_project'}, 'auth_cred': {'username': 'test_user',
'password':
'test_password'},
'name': 'VIM0'}}
vim_type = 'openstack'
res = self.nfvo_plugin.create_vim(self.context, vim_dict)
self._driver_manager.invoke.assert_called_once_with(vim_type,
'register_vim',
vim_obj=vim_dict[
'vim'])
self.assertIsNotNone(res)
self.assertIn('id', res)
self.assertIn('placement_attr', res)
def test_delete_vim(self):
self._insert_dummy_vim()
vim_type = 'openstack'
vim_id = '6261579e-d6f3-49ad-8bc3-a9cb974778ff'
self.nfvo_plugin.delete_vim(self.context, vim_id)
self._driver_manager.invoke.assert_called_once_with(vim_type,
'deregister_vim',
vim_id=vim_id)
def test_update_vim(self):
vim_dict = {'vim': {'id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'vim_project': {'name': 'new_project'},
'auth_cred': {'username': 'new_user',
'password': 'new_password'}}}
vim_type = 'openstack'
vim_auth_username = vim_dict['vim']['auth_cred']['username']
vim_auth_password = vim_dict['vim']['auth_cred']['password']
vim_project = vim_dict['vim']['vim_project']
self._insert_dummy_vim()
res = self.nfvo_plugin.update_vim(self.context, vim_dict['vim']['id'],
vim_dict)
self._driver_manager.invoke.assert_called_once_with(vim_type,
'register_vim',
vim_obj=mock.ANY)
self.assertIsNotNone(res)
self.assertIn('id', res)
self.assertIn('placement_attr', res)
self.assertEqual(vim_project, res['vim_project'])
self.assertEqual(vim_auth_username, res['auth_cred']['username'])
self.assertEqual(vim_auth_password, res['auth_cred']['password'])

View File

@ -18,6 +18,7 @@ import uuid
import mock
from tacker import context
from tacker.db.nfvo import nfvo_db
from tacker.db.vm import vm_db
from tacker.extensions import vnfm
from tacker.tests.unit.db import base as db_base
@ -39,14 +40,21 @@ class FakeGreenPool(mock.Mock):
pass
class FakeVimClient(mock.Mock):
pass
class TestVNFMPlugin(db_base.SqlTestCase):
def setUp(self):
super(TestVNFMPlugin, self).setUp()
self.addCleanup(mock.patch.stopall)
self.context = context.get_admin_context()
self._mock_vim_client()
self._stub_get_vim()
self._mock_device_manager()
self._mock_vnf_monitor()
self._mock_green_pool()
self._insert_dummy_vim()
self.vnfm_plugin = plugin.VNFMPlugin()
def _mock_device_manager(self):
@ -58,12 +66,20 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self._mock(
'tacker.common.driver_manager.DriverManager', fake_device_manager)
def _mock_vnf_monitor(self):
self._vnf_monitor = mock.Mock(wraps=FakeVNFMonitor())
fake_vnf_monitor = mock.Mock()
fake_vnf_monitor.return_value = self._vnf_monitor
def _mock_vim_client(self):
self.vim_client = mock.Mock(wraps=FakeVimClient())
fake_vim_client = mock.Mock()
fake_vim_client.return_value = self.vim_client
self._mock(
'tacker.vm.monitor.VNFMonitor', fake_vnf_monitor)
'tacker.vm.vim_client.VimClient', fake_vim_client)
def _stub_get_vim(self):
vim_obj = {'vim_id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'vim_name': 'fake_vim', 'vim_auth':
{'auth_url': 'http://localhost:5000', 'password':
'test_pw', 'username': 'test_user', 'project_name':
'test_project'}}
self.vim_client.get_vim.return_value = vim_obj
def _mock_green_pool(self):
self._pool = mock.Mock(wraps=FakeGreenPool())
@ -72,9 +88,12 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self._mock(
'eventlet.GreenPool', fake_green_pool)
def _mock(self, target, new=mock.DEFAULT):
patcher = mock.patch(target, new)
return patcher.start()
def _mock_vnf_monitor(self):
self._vnf_monitor = mock.Mock(wraps=FakeVNFMonitor())
fake_vnf_monitor = mock.Mock()
fake_vnf_monitor.return_value = self._vnf_monitor
self._mock(
'tacker.vm.monitor.VNFMonitor', fake_vnf_monitor)
def _insert_dummy_device_template(self):
session = self.context.session
@ -98,11 +117,33 @@ class TestVNFMPlugin(db_base.SqlTestCase):
description='fake_device_description',
instance_id='da85ea1a-4ec4-4201-bbb2-8d9249eca7ec',
template_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
placement_attr={'region': 'RegionOne'},
status='ACTIVE')
session.add(device_db)
session.flush()
return device_db
def _insert_dummy_vim(self):
session = self.context.session
vim_db = nfvo_db.Vim(
id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='fake_vim',
description='fake_vim_description',
type='openstack',
placement_attr={'regions': ['RegionOne']})
vim_auth_db = nfvo_db.VimAuth(
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
password='encrypted_pw',
auth_url='http://localhost:5000',
vim_project={'name': 'test_project'},
auth_cred={'username': 'test_user', 'user_domain_id': 'default',
'project_domain_d': 'default'})
session.add(vim_db)
session.add(vim_auth_db)
session.flush()
def test_create_vnfd(self):
vnfd_obj = utils.get_dummy_vnfd_obj()
result = self.vnfm_plugin.create_vnfd(self.context, vnfd_obj)
@ -144,7 +185,8 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self._device_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
plugin=mock.ANY,
context=mock.ANY,
device=mock.ANY)
device=mock.ANY,
auth_attr=mock.ANY)
self._pool.spawn_n.assert_called_once_with(mock.ANY)
def test_delete_vnf(self):
@ -155,10 +197,12 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self._device_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
plugin=mock.ANY,
context=mock.ANY,
device_id=mock.ANY)
device_id=mock.ANY,
auth_attr=mock.ANY,
region_name=mock.ANY)
self._vnf_monitor.delete_hosting_vnf.assert_called_with(mock.ANY)
self._pool.spawn_n.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY)
mock.ANY, mock.ANY)
def test_update_vnf(self):
self._insert_dummy_device_template()
@ -173,4 +217,4 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self.assertIn('attributes', result)
self.assertIn('mgmt_url', result)
self._pool.spawn_n.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY)
mock.ANY, mock.ANY)

View File

@ -174,11 +174,12 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
@log.log
def _process_vdu_network_interfaces(self, vdu_id, vdu_dict, properties,
template_dict):
def make_port_dict():
port_dict = {
'type': 'OS::Neutron::Port',
'properties': {
'port_security_enabled': False
'port_security_enabled': False
}
}
port_dict['properties'].setdefault('fixed_ips', [])
@ -229,9 +230,8 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
networks_list.append(dict(network_param))
@log.log
def create(self, plugin, context, device):
def create(self, plugin, context, device, auth_attr):
LOG.debug(_('device %s'), device)
heatclient_ = HeatClient(context)
attributes = device['device_template']['attributes'].copy()
vnfd_yaml = attributes.pop('vnfd', None)
fields = dict((key, attributes.pop(key)) for key
@ -251,6 +251,9 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
fields.setdefault(key, {}).update(
jsonutils.loads(dev_attrs.pop(key)))
region_name = device.get('placement_attr', {}).get('region_name', None)
heatclient_ = HeatClient(auth_attr, region_name)
LOG.debug('vnfd_yaml %s', vnfd_yaml)
if vnfd_yaml is not None:
vnfd_dict = yamlparser.simple_ordered_parse(vnfd_yaml)
@ -381,8 +384,10 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
stack = heatclient_.create(fields)
return stack['stack']['id']
def create_wait(self, plugin, context, device_dict, device_id):
heatclient_ = HeatClient(context)
def create_wait(self, plugin, context, device_dict, device_id, auth_attr):
region_name = device_dict.get('placement_attr', {}).get(
'region_name', None)
heatclient_ = HeatClient(auth_attr, region_name)
stack = heatclient_.get(device_id)
status = stack.stack_status
@ -422,9 +427,11 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
device_dict['mgmt_url'] = jsonutils.dumps(mgmt_ips)
@log.log
def update(self, plugin, context, device_id, device_dict, device):
# checking if the stack exists at the moment
heatclient_ = HeatClient(context)
def update(self, plugin, context, device_id, device_dict, device,
auth_attr):
region_name = device_dict.get('placement_attr', {}).get(
'region_name', None)
heatclient_ = HeatClient(auth_attr, region_name)
heatclient_.get(device_id)
# update config attribute
@ -461,18 +468,20 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
new_yaml = yaml.dump(config_dict)
device_dict.setdefault('attributes', {})['config'] = new_yaml
def update_wait(self, plugin, context, device_id):
def update_wait(self, plugin, context, device_id, auth_attr,
region_name=None):
# do nothing but checking if the stack exists at the moment
heatclient_ = HeatClient(context)
heatclient_ = HeatClient(auth_attr, region_name)
heatclient_.get(device_id)
def delete(self, plugin, context, device_id):
heatclient_ = HeatClient(context)
def delete(self, plugin, context, device_id, auth_attr, region_name=None):
heatclient_ = HeatClient(auth_attr, region_name)
heatclient_.delete(device_id)
@log.log
def delete_wait(self, plugin, context, device_id):
heatclient_ = HeatClient(context)
def delete_wait(self, plugin, context, device_id, auth_attr,
region_name=None):
heatclient_ = HeatClient(auth_attr, region_name)
stack = heatclient_.get(device_id)
status = stack.stack_status
@ -513,9 +522,11 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
class HeatClient(object):
def __init__(self, context, password=None):
def __init__(self, auth_attr, region_name=None):
# context, password are unused
self.stacks = clients.OpenstackClients().heat.stacks
self.heat = clients.OpenstackClients(auth_attr, region_name).heat
self.stacks = self.heat.stacks
self.resource_types = self.heat.resource_types
def create(self, fields):
fields = fields.copy()

86
tacker/vm/keystone.py Normal file
View File

@ -0,0 +1,86 @@
# 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 cryptography.fernet import Fernet
from keystoneclient.auth import identity
from keystoneclient import client
from keystoneclient import exceptions
from keystoneclient import session
from oslo_config import cfg
from tacker.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class Keystone(object):
"""Keystone module for OpenStack VIM
Handles identity operations for a given OpenStack
instance such as version, session and client
"""
def get_version(self, base_url=None):
try:
keystone_client = client.Client(auth_url=base_url)
except exceptions.ConnectionRefused:
raise
return keystone_client.version
def get_session(self, auth_plugin):
ses = session.Session(auth=auth_plugin)
return ses
def get_endpoint(self, ses, service_type, region_name=None):
return ses.get_endpoint(service_type, region_name)
def initialize_client(self, version, **kwargs):
if version == 'v2.0':
from keystoneclient.v2_0 import client
auth_plugin = identity.v2.Password(**kwargs)
else:
from keystoneclient.v3 import client
auth_plugin = identity.v3.Password(**kwargs)
ses = self.get_session(auth_plugin=auth_plugin)
try:
cli = client.Client(session=ses)
return cli
except (exceptions.AuthorizationFailure,
exceptions.Unauthorized):
LOG.warn(_("Authorization failed for user"))
raise
@staticmethod
def create_key_dir(path):
if not os.access(path, os.F_OK):
LOG.info(_(
'[fernet_tokens] key_repository does not appear to exist; '
'attempting to create it'))
try:
os.makedirs(path, 0o700)
except OSError:
LOG.error(_(
'Failed to create [fernet_tokens] key_repository: either'
'it already exists or you don\'t have sufficient'
'permissions to create it'))
def create_fernet_key(self):
fernet_key = Fernet.generate_key()
fernet_obj = Fernet(fernet_key)
return fernet_key, fernet_obj

View File

@ -239,7 +239,7 @@ class ActionRespawn(ActionPolicy):
@ActionPolicy.register('respawn', 'heat')
class ActionRespawnHeat(ActionPolicy):
@classmethod
def execute_action(cls, plugin, device_dict):
def execute_action(cls, plugin, device_dict, auth_attr):
device_id = device_dict['id']
LOG.error(_('device %s dead'), device_id)
if plugin._mark_device_dead(device_dict['id']):
@ -257,25 +257,19 @@ class ActionRespawnHeat(ActionPolicy):
attributes = device_dict['attributes'].copy()
attributes['dead_device_id'] = device_id
new_device = {'id': new_device_id, 'attributes': attributes}
for key in ('tenant_id', 'template_id', 'name'):
for key in ('tenant_id', 'template_id', 'name', 'vim_id',
'placement_attr'):
new_device[key] = device_dict[key]
LOG.debug(_('new_device %s'), new_device)
placement_attr = device_dict.get('placement_attr', {})
region_name = placement_attr.get('region_name', None)
# kill heat stack
heatclient = heat.HeatClient(None)
heatclient = heat.HeatClient(auth_attr=auth_attr,
region_name=region_name)
heatclient.delete(device_dict['instance_id'])
# keystone v2.0 specific
authtoken = CONF.keystone_authtoken
token = clients.OpenstackClients().auth_token
# TODO(anyone) set the current request ctxt instead of admin ctxt
context = t_context.get_admin_context()
context.tenant_name = authtoken.project_name
context.user_name = authtoken.username
context.auth_token = token['id']
context.tenant_id = token['tenant_id']
context.user_id = token['user_id']
new_device_dict = plugin.create_device_sync(
context, {'device': new_device})
LOG.info(_('respawned new device %s'), new_device_dict['id'])
@ -295,7 +289,7 @@ class ActionRespawnHeat(ActionPolicy):
new_device_dict.setdefault('attributes', {})['config'] = config
plugin.config_device(context, new_device_dict)
plugin.add_device_to_monitor(new_device_dict)
plugin.add_device_to_monitor(new_device_dict, auth_attr)
@ActionPolicy.register('log')

View File

@ -28,14 +28,16 @@ from tacker.api.v1 import attributes
from tacker.common import driver_manager
from tacker.db.vm import vm_db
from tacker.extensions import vnfm
from tacker.i18n import _LE
from tacker.openstack.common import excutils
from tacker.openstack.common.gettextutils import _LE
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
from tacker.vm.mgmt_drivers import constants as mgmt_constants
from tacker.vm import monitor
from tacker.vm import vim_client
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class VNFMMgmtMixin(object):
@ -46,7 +48,7 @@ class VNFMMgmtMixin(object):
'Hosting Device/logical service '
'instance tacker plugin will use')),
cfg.IntOpt('boot_wait', default=30,
help=_('Time interval to wait for VM to boot')),
help=_('Time interval to wait for VM to boot'))
]
cfg.CONF.register_opts(OPTS, 'tacker')
@ -115,6 +117,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
super(VNFMPlugin, self).__init__()
self._pool = eventlet.GreenPool()
self.boot_wait = cfg.CONF.tacker.boot_wait
self.vim_client = vim_client.VimClient()
self._device_manager = driver_manager.DriverManager(
'tacker.tacker.device.drivers',
cfg.CONF.tacker.infra_driver)
@ -160,7 +163,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
###########################################################################
# hosting device
def add_device_to_monitor(self, device_dict):
def add_device_to_monitor(self, device_dict, vim_auth):
dev_attrs = device_dict['attributes']
mgmt_url = device_dict['mgmt_url']
if 'monitoring_policy' in dev_attrs and mgmt_url:
@ -168,7 +171,8 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
action_cls = monitor.ActionPolicy.get_policy(action,
device_dict)
if action_cls:
action_cls.execute_action(self, hosting_vnf['device'])
action_cls.execute_action(self, hosting_vnf['device'],
vim_auth)
hosting_vnf = self._vnf_monitor.to_hosting_vnf(
device_dict, action_cb)
@ -189,7 +193,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
}
self.update_device(context, device_id, update)
def _create_device_wait(self, context, device_dict):
def _create_device_wait(self, context, device_dict, auth_attr):
driver_name = self._infra_driver_name(device_dict)
device_id = device_dict['id']
instance_id = self._instance_id(device_dict)
@ -198,7 +202,8 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
try:
self._device_manager.invoke(
driver_name, 'create_wait', plugin=self, context=context,
device_dict=device_dict, device_id=instance_id)
device_dict=device_dict, device_id=instance_id,
auth_attr=auth_attr)
except vnfm.DeviceCreateWaitFailed:
LOG.error(_LE("VNF Create failed for vnf_id %s"), device_id)
create_failed = True
@ -233,7 +238,16 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
device_dict['status'] = new_status
self._create_device_status(context, device_id, new_status)
def _create_device(self, context, device):
def get_vim(self, context, device):
region_name = device.setdefault('placement_attr', {}).get(
'region_name', None)
vim_res = self.vim_client.get_vim(context, device['vim_id'],
region_name)
device['placement_attr']['vim_name'] = vim_res['vim_name']
device['vim_id'] = vim_res['vim_id']
return vim_res['vim_auth']
def _create_device(self, context, device, vim_auth):
device_dict = self._create_device_pre(context, device)
device_id = device_dict['id']
driver_name = self._infra_driver_name(device_dict)
@ -242,7 +256,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
try:
instance_id = self._device_manager.invoke(
driver_name, 'create', plugin=self,
context=context, device=device_dict)
context=context, device=device_dict, auth_attr=vim_auth)
except Exception:
with excutils.save_and_reraise_exception():
self.delete_device(context, device_id)
@ -251,16 +265,17 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
self._create_device_post(context, device_id, None, None,
device_dict)
return
device_dict['instance_id'] = instance_id
return device_dict
def create_device(self, context, device):
device_dict = self._create_device(context, device)
device_info = device['device']
vim_auth = self.get_vim(context, device_info)
device_dict = self._create_device(context, device_info, vim_auth)
def create_device_wait():
self._create_device_wait(context, device_dict)
self.add_device_to_monitor(device_dict)
self._create_device_wait(context, device_dict, vim_auth)
self.add_device_to_monitor(device_dict, vim_auth)
self.config_device(context, device_dict)
self.spawn_n(create_device_wait)
return device_dict
@ -268,11 +283,13 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
# not for wsgi, but for service to create hosting device
# the device is NOT added to monitor.
def create_device_sync(self, context, device):
device_dict = self._create_device(context, device)
self._create_device_wait(context, device_dict)
device_info = device['device']
vim_auth = self.get_vim(context, device_info)
device_dict = self._create_device(context, device_info, vim_auth)
self._create_device_wait(context, device_dict, vim_auth)
return device_dict
def _update_device_wait(self, context, device_dict):
def _update_device_wait(self, context, device_dict, vim_auth):
driver_name = self._infra_driver_name(device_dict)
instance_id = self._instance_id(device_dict)
kwargs = {
@ -280,10 +297,14 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
mgmt_constants.KEY_KWARGS: {'device': device_dict},
}
new_status = constants.ACTIVE
placement_attr = device_dict['placement_attr']
region_name = placement_attr.get('region_name', None)
try:
self._device_manager.invoke(
driver_name, 'update_wait', plugin=self,
context=context, device_id=instance_id)
context=context, device_id=instance_id, auth_attr=vim_auth,
region_name=region_name)
self.mgmt_call(context, device_dict, kwargs)
except Exception:
LOG.exception(_('_update_device_wait'))
@ -296,6 +317,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
def update_device(self, context, device_id, device):
device_dict = self._update_device_pre(context, device_id)
vim_auth = self.get_vim(context, device_dict)
driver_name = self._infra_driver_name(device_dict)
instance_id = self._instance_id(device_dict)
@ -303,25 +325,28 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
self.mgmt_update_pre(context, device_dict)
self._device_manager.invoke(
driver_name, 'update', plugin=self, context=context,
device_id=instance_id, device_dict=device_dict, device=device)
device_id=instance_id, device_dict=device_dict,
device=device, auth_attr=vim_auth)
except Exception:
with excutils.save_and_reraise_exception():
device_dict['status'] = constants.ERROR
self.mgmt_update_post(context, device_dict)
self._update_device_post(context, device_id, constants.ERROR)
self.spawn_n(self._update_device_wait, context, device_dict)
self.spawn_n(self._update_device_wait, context, device_dict, vim_auth)
return device_dict
def _delete_device_wait(self, context, device_dict):
def _delete_device_wait(self, context, device_dict, auth_attr):
driver_name = self._infra_driver_name(device_dict)
instance_id = self._instance_id(device_dict)
e = None
placement_attr = device_dict['placement_attr']
region_name = placement_attr.get('region_name', None)
try:
self._device_manager.invoke(
driver_name, 'delete_wait', plugin=self,
context=context, device_id=instance_id)
context=context, device_id=instance_id, auth_attr=auth_attr,
region_name=region_name)
except Exception as e_:
e = e_
device_dict['status'] = constants.ERROR
@ -332,10 +357,12 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
def delete_device(self, context, device_id):
device_dict = self._delete_device_pre(context, device_id)
vim_auth = self.get_vim(context, device_dict)
self._vnf_monitor.delete_hosting_vnf(device_id)
driver_name = self._infra_driver_name(device_dict)
instance_id = self._instance_id(device_dict)
placement_attr = device_dict['placement_attr']
region_name = placement_attr.get('region_name', None)
kwargs = {
mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_DELETE_DEVICE,
mgmt_constants.KEY_KWARGS: {'device': device_dict},
@ -344,7 +371,10 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
self.mgmt_delete_pre(context, device_dict)
self.mgmt_call(context, device_dict, kwargs)
self._device_manager.invoke(driver_name, 'delete', plugin=self,
context=context, device_id=instance_id)
context=context,
device_id=instance_id,
auth_attr=vim_auth,
region_name=region_name)
except Exception as e:
# TODO(yamahata): when the devaice is already deleted. mask
# the error, and delete row in db
@ -355,7 +385,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
self._delete_device_post(context, device_id, e)
self._delete_device_post(context, device_id, None)
self.spawn_n(self._delete_device_wait, context, device_dict)
self.spawn_n(self._delete_device_wait, context, device_dict, vim_auth)
def create_vnf(self, context, vnf):
vnf['device'] = vnf.pop('vnf')

107
tacker/vm/vim_client.py Normal file
View File

@ -0,0 +1,107 @@
# Copyright 2015-2016 Brocade Communications Systems 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 cryptography.fernet import Fernet
from oslo_config import cfg
from tacker.extensions import nfvo
from tacker import manager
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
OPTS = [
cfg.StrOpt(
'default_vim', help=_('Default VIM for launching VNFs'))
]
cfg.CONF.register_opts(OPTS, 'nfvo_vim')
class VimClient(object):
def get_vim(self, context, vim_id=None, region_name=None):
"""Get Vim information for provided VIM id
Initiate the NFVO plugin, request VIM information for the provided
VIM id and validate region
"""
nfvo_plugin = manager.TackerManager.get_service_plugins().get(
constants.NFVO)
if not vim_id:
LOG.debug(_('VIM id not provided. Attempting to find default '
'VIM id'))
vim_name = cfg.CONF.nfvo_vim.default_vim
if not vim_name:
raise nfvo.VimDefaultIdException(
message='Default VIM is not specified. Either specify a '
'valid VIM in the VNF create or set default VIM in'
' tacker.conf')
try:
vim_info = nfvo_plugin.get_vim_by_name(context, vim_name)
except Exception:
raise nfvo.VimDefaultIdException(
vim_name=vim_name)
else:
try:
vim_info = nfvo_plugin.get_vim(context, vim_id)
except Exception:
raise nfvo.VimNotFoundException(vim_id=vim_id)
LOG.debug(_('VIM info found for vim id %s'), vim_id)
if region_name and not self.region_valid(vim_info['placement_attr']
['regions'], region_name):
raise nfvo.VimRegionNotFoundException(region_name=region_name)
vim_auth = self._build_vim_auth(vim_info)
vim_res = {'vim_auth': vim_auth, 'vim_id': vim_info['id'],
'vim_name': vim_info.get('name', vim_info['id'])}
return vim_res
@staticmethod
def region_valid(vim_regions, region_name):
return region_name in vim_regions
def _build_vim_auth(self, vim_info):
LOG.debug('VIM id is %s', vim_info['id'])
vim_auth = vim_info['auth_cred']
vim_auth['password'] = self._decode_vim_auth(vim_info['id'],
vim_auth[
'password'].encode(
'utf-8'))
vim_auth['auth_url'] = vim_info['auth_url']
return vim_auth
def _decode_vim_auth(self, vim_id, cred):
"""Decode Vim credentials
Decrypt VIM cred. using Fernet Key
"""
vim_key = self._find_vim_key(vim_id)
f = Fernet(vim_key)
if not f:
LOG.warn(_('Unable to decode VIM auth'))
raise nfvo.VimNotFoundException('Unable to decode VIM auth key')
return f.decrypt(cred)
@staticmethod
def _find_vim_key(vim_id):
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
LOG.debug(_('Attempting to open key file for vim id %s'), vim_id)
with open(key_file, 'r') as f:
return f.read()
LOG.warn(_('VIM id invalid or key not found for %s'), vim_id)