246 lines
8.1 KiB
Python
246 lines
8.1 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
from iotronic.common import exception
|
|
from iotronic.common.i18n import _
|
|
from keystoneauth1 import identity
|
|
from keystoneauth1 import session
|
|
from neutronclient.common import exceptions as neutron_exceptions
|
|
from neutronclient.v2_0 import client as clientv20
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
|
|
CONF = cfg.CONF
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
neutron_opts = [
|
|
cfg.StrOpt('url',
|
|
default='http://localhost:9696/',
|
|
help=('URL neutron')),
|
|
cfg.StrOpt('retries',
|
|
default=3,
|
|
help=('retries neutron')),
|
|
cfg.StrOpt('auth_strategy',
|
|
default='noauth',
|
|
help=('auth_strategy neutron')),
|
|
cfg.StrOpt('username',
|
|
default='neutron',
|
|
help=('neutron username')),
|
|
cfg.StrOpt('password',
|
|
default='',
|
|
help=('password')),
|
|
cfg.StrOpt('project_name',
|
|
default='service',
|
|
help=('service')),
|
|
cfg.StrOpt('project_domain_name',
|
|
default='default',
|
|
help=('domain id')),
|
|
cfg.StrOpt('auth_url',
|
|
default='http://localhost:35357',
|
|
help=('auth')),
|
|
cfg.StrOpt('project_domain_id',
|
|
default='default',
|
|
help=('project domain id')),
|
|
cfg.StrOpt('user_domain_id',
|
|
default='default',
|
|
help=('user domain id')),
|
|
]
|
|
|
|
CONF.register_opts(neutron_opts, 'neutron')
|
|
|
|
DEFAULT_NEUTRON_URL = CONF.neutron.url
|
|
|
|
_NEUTRON_SESSION = None
|
|
|
|
"""def _get_neutron_session():
|
|
global _NEUTRON_SESSION
|
|
if not _NEUTRON_SESSION:
|
|
_NEUTRON_SESSION = keystone.get_session('neutron')
|
|
return _NEUTRON_SESSION
|
|
"""
|
|
|
|
|
|
def get_client(token=None):
|
|
auth = identity.Password(auth_url=CONF.neutron.auth_url,
|
|
username=CONF.neutron.username,
|
|
password=CONF.neutron.password,
|
|
project_name=CONF.neutron.project_name,
|
|
project_domain_id=CONF.neutron.project_domain_id,
|
|
user_domain_id=CONF.neutron.user_domain_id)
|
|
sess = session.Session(auth=auth)
|
|
neutron = clientv20.Client(session=sess)
|
|
return neutron
|
|
|
|
|
|
def subnet_info(subnet_uuid):
|
|
"""Get subnet details : we need the CIDR
|
|
|
|
:param subnet_uuid: Neutron subnet UUID.
|
|
"""
|
|
|
|
client = get_client()
|
|
try:
|
|
info = client.show_subnet(subnet_uuid)
|
|
return info
|
|
except Exception as e:
|
|
LOG.error(str(e))
|
|
|
|
|
|
def unbind_neutron_port(port_id, client=None):
|
|
"""Unbind a neutron port
|
|
|
|
Remove a neutron port's binding profile and host ID so that it returns to
|
|
an unbound state.
|
|
|
|
:param port_id: Neutron port ID.
|
|
:param client: Optional a Neutron client object.
|
|
:raises: NetworkError
|
|
"""
|
|
|
|
if not client:
|
|
client = get_client()
|
|
|
|
body = {'port': {'binding:host_id': '',
|
|
'binding:profile': {}}}
|
|
|
|
try:
|
|
client.update_port(port_id, body)
|
|
# NOTE(vsaienko): Ignore if port was deleted before calling vif detach.
|
|
except neutron_exceptions.PortNotFoundClient:
|
|
LOG.info('Port %s was not found while unbinding.', port_id)
|
|
except neutron_exceptions.NeutronClientException as e:
|
|
msg = (_('Unable to clear binding profile for '
|
|
'neutron port %(port_id)s. Error: '
|
|
'%(err)s') % {'port_id': port_id, 'err': e})
|
|
LOG.exception(msg)
|
|
raise exception.NetworkError(msg)
|
|
|
|
|
|
def update_port_address(port_id, address):
|
|
"""Update a port's mac address.
|
|
|
|
:param port_id: Neutron port id.
|
|
:param address: new MAC address.
|
|
:raises: FailedToUpdateMacOnPort
|
|
"""
|
|
client = get_client()
|
|
port_req_body = {'port': {'mac_address': address}}
|
|
|
|
try:
|
|
msg = (_("Failed to get the current binding on Neutron "
|
|
"port %s.") % port_id)
|
|
port = client.show_port(port_id).get('port', {})
|
|
binding_host_id = port.get('binding:host_id')
|
|
binding_profile = port.get('binding:profile')
|
|
|
|
if binding_host_id:
|
|
# Unbind port before we update it's mac address, because you can't
|
|
# change a bound port's mac address.
|
|
msg = (_("Failed to remove the current binding from "
|
|
"Neutron port %s, while updating its MAC "
|
|
"address.") % port_id)
|
|
unbind_neutron_port(port_id, client=client)
|
|
port_req_body['port']['binding:host_id'] = binding_host_id
|
|
port_req_body['port']['binding:profile'] = binding_profile
|
|
|
|
msg = (_("Failed to update MAC address on Neutron port %s.") % port_id)
|
|
client.update_port(port_id, port_req_body)
|
|
except (neutron_exceptions.NeutronClientException, exception.NetworkError):
|
|
LOG.exception(msg)
|
|
raise exception.FailedToUpdateMacOnPort(port_id=port_id)
|
|
|
|
|
|
def _verify_security_groups(security_groups, client):
|
|
"""Verify that the security groups exist.
|
|
|
|
:param security_groups: a list of security group UUIDs; may be None or
|
|
empty
|
|
:param client: Neutron client
|
|
:raises: NetworkError
|
|
"""
|
|
|
|
if not security_groups:
|
|
return
|
|
try:
|
|
neutron_sec_groups = (
|
|
client.list_security_groups().get('security_groups', []))
|
|
except neutron_exceptions.NeutronClientException as e:
|
|
msg = (_("Could not retrieve security groups from neutron: %(exc)s") %
|
|
{'exc': e})
|
|
LOG.exception(msg)
|
|
raise exception.NetworkError(msg)
|
|
|
|
existing_sec_groups = [sec_group['id'] for sec_group in neutron_sec_groups]
|
|
missing_sec_groups = set(security_groups) - set(existing_sec_groups)
|
|
if missing_sec_groups:
|
|
msg = (_('Could not find these security groups '
|
|
'(specified via iotronic '
|
|
'config) in neutron: %(ir-sg)s')
|
|
% {'ir-sg': list(missing_sec_groups)})
|
|
LOG.error(msg)
|
|
raise exception.NetworkError(msg)
|
|
|
|
|
|
def add_port_to_network(board, network_uuid, subnet_uuid,
|
|
security_groups=None):
|
|
|
|
client = get_client()
|
|
_verify_security_groups(security_groups, client)
|
|
LOG.debug('For wagent %(wagent)s, creating neutron port on network '
|
|
'%(network_uuid)s.',
|
|
{'wagent': board.agent, 'network_uuid': network_uuid})
|
|
|
|
body = {
|
|
'port': {
|
|
'network_id': network_uuid,
|
|
'project_id': board.project,
|
|
'device_id': board.uuid,
|
|
'admin_state_up': True,
|
|
'device_owner': 'iot:board',
|
|
'binding:host_id': board.agent,
|
|
'fixed_ips': [{
|
|
'subnet_id': subnet_uuid
|
|
}]
|
|
}
|
|
}
|
|
|
|
if security_groups:
|
|
body['port']['security_groups'] = security_groups
|
|
|
|
try:
|
|
port = client.create_port(body)
|
|
except neutron_exceptions.NeutronClientException as e:
|
|
LOG.warning("Could not create neutron port for wagent's "
|
|
"%(wagent)s on the neutron "
|
|
"network %(net)s. %(exc)s",
|
|
{'net': network_uuid, 'wagent': board.agent,
|
|
'exc': e})
|
|
else:
|
|
return port
|
|
|
|
|
|
def delete_port(wagent, port_uuid):
|
|
|
|
client = get_client()
|
|
LOG.debug('For wagent %(wagent)s, removing neutron port %(port_uuid)s',
|
|
{'wagent': wagent, 'port_uuid': port_uuid})
|
|
try:
|
|
client.delete_port(port_uuid)
|
|
return 1
|
|
|
|
except neutron_exceptions.NeutronClientException as e:
|
|
LOG.warning("Could not delete neutron port from wagent's "
|
|
"%(wagent)s : %(exc)s ", {'wagent': wagent, 'exc': e})
|
|
return 0
|