278 lines
10 KiB
Python
278 lines
10 KiB
Python
# Copyright 2013, 2014 Intel Corporation.
|
|
# 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 time
|
|
|
|
|
|
from keystoneclient import auth as ks_auth
|
|
from keystoneclient.auth.identity import v2 as v2_auth
|
|
from keystoneclient import session as ks_session
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from six import iteritems
|
|
|
|
from tacker.api.v1 import attributes
|
|
from tacker.i18n import _LE, _LW
|
|
from tacker.openstack.common import versionutils
|
|
from tacker.vm.infra_drivers import abstract_driver
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
NOVA_API_VERSION = "2"
|
|
TACKER_NOVA_CONF_SECTION = 'tacker_nova'
|
|
ks_session.Session.register_conf_options(cfg.CONF, TACKER_NOVA_CONF_SECTION)
|
|
ks_auth.register_conf_options(cfg.CONF, TACKER_NOVA_CONF_SECTION)
|
|
OPTS = [
|
|
cfg.StrOpt('region_name',
|
|
help=_('Name of nova region to use. Useful if keystone manages'
|
|
' more than one region.')),
|
|
]
|
|
CONF.register_opts(OPTS, group=TACKER_NOVA_CONF_SECTION)
|
|
_NICS = 'nics' # converted by novaclient => 'networks'
|
|
_NET_ID = 'net-id' # converted by novaclient => 'uuid'
|
|
_PORT_ID = 'port-id' # converted by novaclient => 'port'
|
|
_FILES = 'files'
|
|
|
|
|
|
class DefaultAuthPlugin(v2_auth.Password):
|
|
"""A wrapper around standard v2 user/pass to handle bypass url.
|
|
|
|
This is only necessary because novaclient doesn't support endpoint_override
|
|
yet - bug #1403329.
|
|
|
|
When this bug is fixed we can pass the endpoint_override to the client
|
|
instead and remove this class.
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
self._endpoint_override = kwargs.pop('endpoint_override', None)
|
|
super(DefaultAuthPlugin, self).__init__(**kwargs)
|
|
|
|
def get_endpoint(self, session, **kwargs):
|
|
if self._endpoint_override:
|
|
return self._endpoint_override
|
|
|
|
return super(DefaultAuthPlugin, self).get_endpoint(session, **kwargs)
|
|
|
|
|
|
@versionutils.deprecated(
|
|
versionutils.deprecated.MITAKA,
|
|
what='infra_driver nova',
|
|
in_favor_of='infra_driver heat',
|
|
remove_in=+1)
|
|
class DeviceNova(abstract_driver.DeviceAbstractDriver):
|
|
|
|
"""Nova driver of hosting device."""
|
|
|
|
def __init__(self):
|
|
super(DeviceNova, self).__init__()
|
|
# avoid circular import
|
|
from novaclient import client
|
|
self._novaclient = client
|
|
|
|
def _nova_client(self, token=None):
|
|
auth = ks_auth.load_from_conf_options(cfg.CONF,
|
|
TACKER_NOVA_CONF_SECTION)
|
|
endpoint_override = None
|
|
|
|
if not auth:
|
|
LOG.warning(_LW('Authenticating to nova using nova_admin_* options'
|
|
' is deprecated. This should be done using'
|
|
' an auth plugin, like password'))
|
|
|
|
if cfg.CONF.nova_admin_tenant_id:
|
|
endpoint_override = "%s/%s" % (cfg.CONF.nova_url,
|
|
cfg.CONF.nova_admin_tenant_id)
|
|
|
|
auth = DefaultAuthPlugin(
|
|
auth_url=cfg.CONF.nova_admin_auth_url,
|
|
username=cfg.CONF.nova_admin_username,
|
|
password=cfg.CONF.nova_admin_password,
|
|
tenant_id=cfg.CONF.nova_admin_tenant_id,
|
|
tenant_name=cfg.CONF.nova_admin_tenant_name,
|
|
endpoint_override=endpoint_override)
|
|
|
|
session = ks_session.Session.load_from_conf_options(
|
|
cfg.CONF, TACKER_NOVA_CONF_SECTION, auth=auth)
|
|
novaclient_cls = self._novaclient.get_client_class(NOVA_API_VERSION)
|
|
return novaclient_cls(session=session,
|
|
region_name=cfg.CONF.tacker_nova.region_name)
|
|
|
|
def get_type(self):
|
|
return 'nova'
|
|
|
|
def get_name(self):
|
|
return 'nova'
|
|
|
|
def get_description(self):
|
|
return 'Nuetron Device Nova driver'
|
|
|
|
@staticmethod
|
|
def _safe_pop(d, name_list):
|
|
res = None
|
|
for name in name_list:
|
|
if name in d:
|
|
res = d.pop(name)
|
|
break
|
|
return res
|
|
|
|
def _create_port(self, plugin, context, tenant_id,
|
|
network_id=None, subnet_id=None):
|
|
# resolve subnet and create port
|
|
LOG.debug(_('network_id %(network_id)s subnet_id %(subnet_id)s)'),
|
|
{'network_id': network_id, 'subnet_id': subnet_id})
|
|
if subnet_id:
|
|
subnet = plugin._core_plugin.get_subnet(context, subnet_id)
|
|
network_id = subnet['network_id']
|
|
port_data = {
|
|
'tenant_id': tenant_id,
|
|
'network_id': network_id,
|
|
'admin_state_up': True,
|
|
'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
|
|
}
|
|
if subnet_id:
|
|
port_data['fixed_ips'] = [{'subnet_id': subnet_id}]
|
|
|
|
# See api.v2.base.prepare_request_body()
|
|
for attr, attr_vals in iteritems(attributes.RESOURCE_ATTRIBUTE_MAP[
|
|
attributes.PORTS]):
|
|
if not attr_vals.get('allow_post', False):
|
|
continue
|
|
if attr in port_data:
|
|
continue
|
|
port_data[attr] = attr_vals['default']
|
|
|
|
LOG.debug(_('port_data %s'), port_data)
|
|
port = plugin._core_plugin.create_port(context, {'port': port_data})
|
|
LOG.debug(_('port %s'), port)
|
|
return port['id']
|
|
|
|
def create(self, plugin, context, device):
|
|
# typical required arguments are
|
|
# 'name': name string
|
|
# 'image': uuid
|
|
# 'flavir': uuid
|
|
#
|
|
# for details, see the signature of
|
|
# novaclient.v<version>.servers.SeverManager.create()
|
|
|
|
LOG.debug(_('device %s'), device)
|
|
# flavor and image are specially treated by novaclient
|
|
attributes = device['device_template']['attributes'].copy()
|
|
attributes.update(device['kwargs'])
|
|
|
|
name = self._safe_pop(attributes, ('name', ))
|
|
if name is None:
|
|
# TODO(yamahata): appropreate way to generate instance name
|
|
name = (__name__ + ':' + self.__class__.__name__ + '-' +
|
|
device['id'])
|
|
image = self._safe_pop(attributes, ('image', 'imageRef'))
|
|
flavor = self._safe_pop(attributes, ('flavor', 'flavorRef'))
|
|
|
|
files = plugin.mgmt_get_config(context, device)
|
|
if files:
|
|
attributes[_FILES] = files
|
|
|
|
LOG.debug(_('service_context: %s'), device.get('service_context', []))
|
|
tenant_id = device['tenant_id']
|
|
nics = []
|
|
for sc_entry in device.get('service_context', []):
|
|
LOG.debug(_('sc_entry: %s'), sc_entry)
|
|
|
|
# nova API doesn't return tacker port_id.
|
|
# so create port if necessary by hand, and use it explicitly.
|
|
if sc_entry['port_id']:
|
|
LOG.debug(_('port_id %s specified'), sc_entry['port_id'])
|
|
port_id = sc_entry['port_id']
|
|
elif sc_entry['subnet_id']:
|
|
LOG.debug(_('subnet_id %s specified'), sc_entry['subnet_id'])
|
|
port_id = self._create_port(plugin, context, tenant_id,
|
|
subnet_id=sc_entry['subnet_id'])
|
|
elif sc_entry['network_id']:
|
|
LOG.debug(_('network_id %s specified'), sc_entry['network_id'])
|
|
port_id = self._create_port(plugin, context, tenant_id,
|
|
network_id=sc_entry['network_id'])
|
|
else:
|
|
LOG.debug(_('skipping sc_entry %s'), sc_entry)
|
|
continue
|
|
|
|
LOG.debug(_('port_id %s'), port_id)
|
|
port = plugin._core_plugin.get_port(context, port_id)
|
|
sc_entry['network_id'] = port['network_id']
|
|
if not sc_entry['subnet_id'] and port['fixed_ips']:
|
|
sc_entry['subnet_id'] = port['fixed_ips'][0]['subnet_id']
|
|
sc_entry['port_id'] = port_id
|
|
|
|
nics.append({_PORT_ID: port_id})
|
|
|
|
if nics:
|
|
attributes[_NICS] = nics
|
|
LOG.debug(_('nics %(nics)s attributes %(attributes)s'),
|
|
{'nics': nics, 'attributes': attributes})
|
|
|
|
nova = self._nova_client()
|
|
instance = nova.servers.create(name, image, flavor, **attributes)
|
|
return instance.id
|
|
|
|
def create_wait(self, plugin, context, device_dict, device_id):
|
|
nova = self._nova_client()
|
|
instance = nova.servers.get(device_id)
|
|
status = instance.status
|
|
# TODO(yamahata): timeout and error
|
|
while status == 'BUILD':
|
|
time.sleep(5)
|
|
instance = nova.servers.get(instance.id)
|
|
status = instance.status
|
|
LOG.debug(_('status: %s'), status)
|
|
|
|
LOG.debug(_('status: %s'), status)
|
|
if status == 'ERROR':
|
|
raise RuntimeError(_("creation of server %s faild") % device_id)
|
|
|
|
def update(self, plugin, context, device_id, device_dict, device):
|
|
# do nothing but checking if the instance exists at the moment
|
|
nova = self._nova_client()
|
|
nova.servers.get(device_id)
|
|
|
|
def update_wait(self, plugin, context, device_id):
|
|
# do nothing but checking if the instance exists at the moment
|
|
nova = self._nova_client()
|
|
nova.servers.get(device_id)
|
|
|
|
def delete(self, plugin, context, device_id):
|
|
nova = self._nova_client()
|
|
try:
|
|
instance = nova.servers.get(device_id)
|
|
except self._novaclient.exceptions.NotFound:
|
|
LOG.error(_LE("server %s is not found") %
|
|
device_id)
|
|
return
|
|
instance.delete()
|
|
|
|
def delete_wait(self, plugin, context, device_id):
|
|
nova = self._nova_client()
|
|
# TODO(yamahata): timeout and error
|
|
while True:
|
|
try:
|
|
instance = nova.servers.get(device_id)
|
|
LOG.debug(_('instance status %s'), instance.status)
|
|
except self._novaclient.exceptions.NotFound:
|
|
break
|
|
if instance.status == 'ERROR':
|
|
raise RuntimeError(_("deletion of server %s faild") %
|
|
device_id)
|
|
time.sleep(5)
|