Virtual-networks
Change-Id: Ia426970fbece3f0c18bdf0ee555c37407dfbe981
This commit is contained in:
parent
737883be4b
commit
67b15a6b7a
@ -36,3 +36,17 @@ user_domain_id = default
|
||||
project_name = service
|
||||
username = iotronic
|
||||
password = <password>
|
||||
|
||||
|
||||
[neutron]
|
||||
url = http://<neutron_host>:9696
|
||||
auth_strategy = password
|
||||
project_domain_name = default
|
||||
user_domain_name = default
|
||||
region_name = RegionOne
|
||||
project_name = service
|
||||
username = neutron
|
||||
password = <password>
|
||||
retries = 3
|
||||
project_domain_id= default
|
||||
auth_url = http://<keystone_host>:35357
|
||||
|
@ -26,9 +26,9 @@ from wsme import types as wtypes
|
||||
from iotronic.api.controllers import base
|
||||
from iotronic.api.controllers import link
|
||||
from iotronic.api.controllers.v1 import plugin
|
||||
from iotronic.api.controllers.v1 import port
|
||||
from iotronic.api.controllers.v1 import service
|
||||
# from iotronic.api.controllers.v1 import driver
|
||||
# from iotronic.api.controllers.v1 import port
|
||||
# from iotronic.api.controllers.v1 import portgroup
|
||||
# from iotronic.api.controllers.v1 import ramdisk
|
||||
# from iotronic.api.controllers.v1 import utils
|
||||
@ -67,6 +67,9 @@ class V1(base.APIBase):
|
||||
services = [link.Link]
|
||||
"""Links to the boards resource"""
|
||||
|
||||
ports = [link.Link]
|
||||
"""Links to the boards resource"""
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
v1 = V1()
|
||||
@ -104,6 +107,12 @@ class V1(base.APIBase):
|
||||
bookmark=True)
|
||||
]
|
||||
|
||||
v1.ports = [link.Link.make_link('self', pecan.request.public_url,
|
||||
'ports', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.public_url, 'ports', '',
|
||||
bookmark=True)]
|
||||
|
||||
return v1
|
||||
|
||||
|
||||
@ -113,6 +122,7 @@ class Controller(rest.RestController):
|
||||
boards = board.BoardsController()
|
||||
plugins = plugin.PluginsController()
|
||||
services = service.ServicesController()
|
||||
ports = port.PortsController()
|
||||
|
||||
@expose.expose(V1)
|
||||
def get(self):
|
||||
|
@ -26,6 +26,10 @@ from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from oslo_log import log as logging
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_DEFAULT_RETURN_FIELDS = ('name', 'code', 'status', 'uuid', 'session', 'type')
|
||||
|
||||
|
||||
@ -117,6 +121,45 @@ class BoardCollection(collection.Collection):
|
||||
return collection
|
||||
|
||||
|
||||
class Port(base.APIBase):
|
||||
|
||||
board_uuid = types.uuid
|
||||
uuid = types.uuid
|
||||
VIF_name = wtypes.text
|
||||
MAC_add = wtypes.text
|
||||
ip = wtypes.text
|
||||
network = wtypes.text
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = []
|
||||
fields = list(objects.Port.fields)
|
||||
fields.remove('board_uuid')
|
||||
for k in fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, k):
|
||||
continue
|
||||
self.fields.append(k)
|
||||
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||
setattr(self, 'uuid', kwargs.get('uuid', wtypes.Unset))
|
||||
|
||||
|
||||
class PortCollection(collection.Collection):
|
||||
|
||||
"""API representation of a collection of ports."""
|
||||
|
||||
ports = [Port]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._type = 'ports'
|
||||
|
||||
@staticmethod
|
||||
def get_list(ports, fields=None):
|
||||
collection = PortCollection()
|
||||
collection.ports = [Port(**n.as_dict())
|
||||
for n in ports]
|
||||
return collection
|
||||
|
||||
|
||||
class InjectionPlugin(base.APIBase):
|
||||
plugin = types.uuid_or_name
|
||||
board_uuid = types.uuid_or_name
|
||||
@ -196,6 +239,12 @@ class ServiceAction(base.APIBase):
|
||||
parameters = types.jsontype
|
||||
|
||||
|
||||
class Network(base.APIBase):
|
||||
network = types.jsontype
|
||||
subnet = types.jsontype
|
||||
security_groups = types.jsontype
|
||||
|
||||
|
||||
class BoardPluginsController(rest.RestController):
|
||||
def __init__(self, board_ident):
|
||||
self.board_ident = board_ident
|
||||
@ -401,12 +450,93 @@ class BoardServicesController(rest.RestController):
|
||||
return self._get_services_on_board_collection(rpc_board.uuid)
|
||||
|
||||
|
||||
class BoardPortsController(rest.RestController):
|
||||
|
||||
def __init__(self, board_ident):
|
||||
self.board_ident = board_ident
|
||||
|
||||
def _get_ports_on_board_collection(self, board_uuid, fields=None):
|
||||
filters = {}
|
||||
filters['board_uuid'] = board_uuid
|
||||
ports = objects.Port.list(pecan.request.context,
|
||||
filters=filters)
|
||||
|
||||
return PortCollection.get_list(ports, fields=fields)
|
||||
|
||||
def get_port_detail(self, board_uuid, port_uuid):
|
||||
filters = {}
|
||||
filters['board_uuid'] = board_uuid
|
||||
ports = objects.Port.list(pecan.request.context,
|
||||
filters=filters)
|
||||
for port in ports:
|
||||
if port.uuid == port_uuid:
|
||||
return port
|
||||
|
||||
@expose.expose(wtypes.text, types.uuid_or_name, body=Network,
|
||||
status_code=200)
|
||||
def put(self, Network):
|
||||
|
||||
if not Network.network:
|
||||
raise exception.MissingParameterValue(
|
||||
("Network is not specified."))
|
||||
|
||||
rpc_board = api_utils.get_rpc_board(self.board_ident)
|
||||
|
||||
rpc_board.check_if_online()
|
||||
|
||||
result = pecan.request.rpcapi.\
|
||||
create_port_on_board(pecan.request.context, rpc_board.uuid,
|
||||
Network.network, Network.subnet,
|
||||
Network.security_groups)
|
||||
return result
|
||||
|
||||
@expose.expose(wtypes.text, types.uuid_or_name,
|
||||
status_code=204)
|
||||
def delete(self, port):
|
||||
|
||||
if not port:
|
||||
raise exception.MissingParameterValue(
|
||||
("Port is not specified."))
|
||||
|
||||
rpc_board = api_utils.get_rpc_board(self.board_ident)
|
||||
rpc_port = api_utils.get_rpc_port(port)
|
||||
|
||||
rpc_board.check_if_online()
|
||||
|
||||
result = pecan.request.rpcapi.remove_port_from_board(
|
||||
pecan.request.context, rpc_board.uuid, rpc_port.uuid)
|
||||
|
||||
return result
|
||||
|
||||
@expose.expose(PortCollection, status_code=200)
|
||||
def get_all(self):
|
||||
|
||||
rpc_board = api_utils.get_rpc_board(self.board_ident)
|
||||
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
cdict['owner'] = rpc_board.owner
|
||||
policy.authorize('iot:port_on_board:get', cdict, cdict)
|
||||
|
||||
return self._get_ports_on_board_collection(rpc_board.uuid)
|
||||
|
||||
@expose.expose(Port, types.uuid_or_name, status_code=200)
|
||||
def get_one(self, port_ident):
|
||||
rpc_board = api_utils.get_rpc_board(self.board_ident)
|
||||
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
cdict['owner'] = rpc_board.owner
|
||||
policy.authorize('iot:port_on_board:get', cdict, cdict)
|
||||
|
||||
return self.get_port_detail(rpc_board, port_ident)
|
||||
|
||||
|
||||
class BoardsController(rest.RestController):
|
||||
"""REST controller for Boards."""
|
||||
|
||||
_subcontroller_map = {
|
||||
'plugins': BoardPluginsController,
|
||||
'services': BoardServicesController,
|
||||
'ports': BoardPortsController,
|
||||
}
|
||||
|
||||
invalid_sort_key_list = ['extra', 'location']
|
||||
@ -630,5 +760,4 @@ class BoardsController(rest.RestController):
|
||||
|
||||
return self._get_boards_collection(status, marker,
|
||||
limit, sort_key, sort_dir,
|
||||
project=project,
|
||||
fields=fields)
|
||||
project=project, fields=fields)
|
||||
|
202
iotronic/api/controllers/v1/port.py
Normal file
202
iotronic/api/controllers/v1/port.py
Normal file
@ -0,0 +1,202 @@
|
||||
# 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.api.controllers import base
|
||||
from iotronic.api.controllers import link
|
||||
from iotronic.api.controllers.v1 import collection
|
||||
from iotronic.api.controllers.v1 import types
|
||||
from iotronic.api.controllers.v1 import utils as api_utils
|
||||
from iotronic.api import expose
|
||||
from iotronic.common import exception
|
||||
from iotronic.common import policy
|
||||
from iotronic import objects
|
||||
|
||||
from oslo_log import log as logging
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
|
||||
_DEFAULT_RETURN_FIELDS = ('uuid', 'board_uuid', 'MAC_add',
|
||||
'VIF_name', 'network', 'ip')
|
||||
|
||||
|
||||
class Port(base.APIBase):
|
||||
"""API representation of a port.
|
||||
|
||||
"""
|
||||
uuid = types.uuid
|
||||
board_uuid = types.uuid
|
||||
MAC_add = wsme.wsattr(wtypes.text)
|
||||
VIF_name = wsme.wsattr(wtypes.text)
|
||||
network = wsme.wsattr(wtypes.text)
|
||||
ip = wsme.wsattr(wtypes.text)
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = []
|
||||
fields = list(objects.Port.fields)
|
||||
for k in fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, k):
|
||||
continue
|
||||
self.fields.append(k)
|
||||
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(port, url, fields=None):
|
||||
port_uuid = port.uuid
|
||||
if fields is not None:
|
||||
port.unset_fields_except(fields)
|
||||
|
||||
port.links = [link.Link.make_link('self', url, 'ports', port_uuid),
|
||||
link.Link.make_link('bookmark', url, 'ports',
|
||||
port_uuid, bookmark=True)]
|
||||
return port
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_port, fields=None):
|
||||
port = Port(**rpc_port.as_dict())
|
||||
|
||||
if fields is not None:
|
||||
api_utils.check_for_invalid_fields(fields, port.as_dict())
|
||||
|
||||
return cls._convert_with_links(port, pecan.request.public_url,
|
||||
fields=fields)
|
||||
|
||||
|
||||
class PortCollection(collection.Collection):
|
||||
"""API representation of a collection of ports."""
|
||||
|
||||
ports = [Port]
|
||||
"""A list containing ports objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._type = 'ports'
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(ports, limit, url=None, fields=None, **kwargs):
|
||||
collection = PortCollection()
|
||||
collection.ports = [Port.convert_with_links(n, fields=fields)
|
||||
for n in ports]
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
|
||||
|
||||
class PortsController(rest.RestController):
|
||||
|
||||
"""REST controller for Ports."""
|
||||
|
||||
# public = PublicPortsController()
|
||||
|
||||
invalid_sort_key_list = ['extra', 'location']
|
||||
|
||||
def _get_ports_collection(self, marker, limit, sort_key, sort_dir,
|
||||
fields=None, with_public=False,
|
||||
all_ports=False):
|
||||
|
||||
limit = api_utils.validate_limit(limit)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.Port.get_by_uuid(pecan.request.context,
|
||||
marker)
|
||||
|
||||
if sort_key in self.invalid_sort_key_list:
|
||||
raise exception.InvalidParameterValue(
|
||||
("The sort_key value %(key)s is an invalid field for "
|
||||
"sorting") % {'key': sort_key})
|
||||
|
||||
filters = {}
|
||||
# if all_ports and not pecan.request.context.is_admin:
|
||||
# msg = ("all_ports parameter can only be used "
|
||||
# "by the administrator.")
|
||||
# raise wsme.exc.ClientSideError(msg,
|
||||
# status_code=400)
|
||||
# else:
|
||||
# if not all_ports:
|
||||
# filters['owner'] = pecan.request.context.user_id
|
||||
# if with_public:
|
||||
# filters['with_public'] = with_public
|
||||
|
||||
ports = objects.Port.list(pecan.request.context, limit, marker_obj,
|
||||
sort_key=sort_key, sort_dir=sort_dir,
|
||||
filters=filters)
|
||||
|
||||
parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
|
||||
|
||||
return PortCollection.convert_with_links(ports, limit,
|
||||
fields=fields, **parameters)
|
||||
|
||||
@expose.expose(PortCollection, types.uuid, int, wtypes.text,
|
||||
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||
def get_all(self, marker=None,
|
||||
limit=None, sort_key='id', sort_dir='asc',
|
||||
fields=None, with_public=False, all_ports=False):
|
||||
"""Retrieve a list of ports.
|
||||
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
This value cannot be larger than the value of max_limit
|
||||
in the [api] section of the ironic configuration, or only
|
||||
max_limit resources will be returned.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
:param with_public: Optional boolean to get also public ports.
|
||||
:param all_ports: Optional boolean to get all the ports.
|
||||
Only for the admin
|
||||
:param fields: Optional, a list with a specified set of fields
|
||||
of the resource to be returned.
|
||||
"""
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
policy.authorize('iot:port_on_board:get', cdict, cdict)
|
||||
|
||||
if fields is None:
|
||||
fields = _DEFAULT_RETURN_FIELDS
|
||||
return self._get_ports_collection(marker,
|
||||
limit, sort_key, sort_dir,
|
||||
with_public=with_public,
|
||||
all_ports=all_ports,
|
||||
fields=fields)
|
||||
|
||||
@expose.expose(Port, types.uuid_or_name, types.listtype)
|
||||
def get_one(self, port_ident, fields=None):
|
||||
"""Retrieve information about the given port.
|
||||
|
||||
:param port_ident: UUID or logical name of a port.
|
||||
:param fields: Optional, a list with a specified set of fields
|
||||
of the resource to be returned.
|
||||
"""
|
||||
|
||||
rpc_port = api_utils.get_rpc_port(port_ident)
|
||||
# if not rpc_port.public:
|
||||
# cdict = pecan.request.context.to_policy_values()
|
||||
# cdict['owner'] = rpc_port.owner
|
||||
# policy.authorize('iot:port:get_one', cdict, cdict)
|
||||
|
||||
return Port.convert_with_links(rpc_port, fields=fields)
|
||||
|
||||
@expose.expose(None, types.uuid_or_name, status_code=204)
|
||||
def delete(self, port_ident):
|
||||
|
||||
rpc_port = api_utils.get_rpc_port(port_ident)
|
||||
board = rpc_port.board_uuid
|
||||
|
||||
pecan.request.rpcapi.remove_port_from_board(pecan.request.context,
|
||||
board, rpc_port.uuid)
|
||||
return
|
@ -147,6 +147,31 @@ def get_rpc_service(service_ident):
|
||||
raise exception.ServiceNotFound(service=service_ident)
|
||||
|
||||
|
||||
def get_rpc_port(port_ident):
|
||||
"""Get the RPC port from the port uuid.
|
||||
|
||||
:param port_ident: the UUID or logical name of a port.
|
||||
|
||||
:returns: The RPC Port.
|
||||
:raises: InvalidUuidOrName if the name or uuid provided is not valid.
|
||||
:raises: portNotFound if the port is not found.
|
||||
"""
|
||||
# Check to see if the port_ident is a valid UUID. If it is, treat it
|
||||
# as a UUID.
|
||||
if uuidutils.is_uuid_like(port_ident):
|
||||
return objects.Port.get_by_uuid(pecan.request.context,
|
||||
port_ident)
|
||||
|
||||
# We can refer to ports by their name, if the client supports it
|
||||
else:
|
||||
return objects.Port.get_by_name(pecan.request.context,
|
||||
port_ident)
|
||||
|
||||
raise exception.InvalidUuidOrName(uuid=port_ident)
|
||||
|
||||
raise exception.PortNottFound(uuid=port_ident)
|
||||
|
||||
|
||||
def is_valid_board_name(name):
|
||||
"""Determine if the provided name is a valid board name.
|
||||
|
||||
|
@ -613,3 +613,15 @@ class ExposedServiceNotFound(NotFound):
|
||||
|
||||
class NoExposedServices(NotFound):
|
||||
message = _("No exposed services on the board %(uuid)s.")
|
||||
|
||||
|
||||
class NoPorts(NotFound):
|
||||
message = _("No ports exist on the board %(uuid)s.")
|
||||
|
||||
|
||||
class NoPortsManaged(NotFound):
|
||||
message = _("No ports are managed by the wamp agent %(id)s.")
|
||||
|
||||
|
||||
class NetworkError(IotronicException):
|
||||
message = _("Network operation failure.")
|
||||
|
245
iotronic/common/neutron.py
Normal file
245
iotronic/common/neutron.py
Normal file
@ -0,0 +1,245 @@
|
||||
# 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='0penstack',
|
||||
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
|
@ -15,6 +15,7 @@
|
||||
|
||||
import _pickle as cpickle
|
||||
from iotronic.common import exception
|
||||
from iotronic.common import neutron
|
||||
from iotronic.common import states
|
||||
from iotronic.conductor.provisioner import Provisioner
|
||||
from iotronic import objects
|
||||
@ -23,13 +24,15 @@ from iotronic.wamp import wampmessage as wm
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging
|
||||
|
||||
import random
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
serializer = objects_base.IotronicObjectSerializer()
|
||||
|
||||
Port = list()
|
||||
|
||||
|
||||
def get_best_agent(ctx):
|
||||
agents = objects.WampAgent.list(ctx, filters={'online': True})
|
||||
@ -365,7 +368,6 @@ class ConductorEndpoint(object):
|
||||
def restore_services_on_board(self, ctx, board_uuid):
|
||||
LOG.info('Restoring the services into the board %s',
|
||||
board_uuid)
|
||||
|
||||
exposed_list = objects.ExposedService.get_by_board_uuid(ctx,
|
||||
board_uuid)
|
||||
|
||||
@ -375,3 +377,115 @@ class ConductorEndpoint(object):
|
||||
(service, exposed.public_port))
|
||||
|
||||
return 0
|
||||
|
||||
def create_port_on_board(self, ctx, board_uuid, network_uuid,
|
||||
subnet_uuid, security_groups=None):
|
||||
|
||||
LOG.info('Creation of a port on the board %s in the network',
|
||||
board_uuid)
|
||||
|
||||
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
||||
port_iotronic = objects.Port(ctx)
|
||||
|
||||
subnet_info = neutron.subnet_info(subnet_uuid)
|
||||
cidr = str(subnet_info['subnet']['cidr'])
|
||||
slash = cidr.split("/", 1)[1]
|
||||
try:
|
||||
|
||||
port = neutron.add_port_to_network(board, network_uuid,
|
||||
subnet_uuid, security_groups)
|
||||
p = str(port['port']['id'])
|
||||
|
||||
port_socat = random.randint(10000, 20000)
|
||||
|
||||
i = 0
|
||||
while i < len(Port):
|
||||
if Port[i] == port_socat:
|
||||
i = 0
|
||||
port_socat = random.randint(10000, 20000)
|
||||
i += 1
|
||||
|
||||
global Port
|
||||
Port.insert(0, port_socat)
|
||||
r_tcp_port = str(port_socat)
|
||||
|
||||
s4t_topic = 'create_tap_interface'
|
||||
full_topic = str(board.agent) + '.' + s4t_topic
|
||||
self.target.topic = full_topic
|
||||
|
||||
try:
|
||||
LOG.info('Creation of the VIF on the board')
|
||||
self.execute_on_board(ctx, board_uuid, "Create_VIF",
|
||||
(r_tcp_port,))
|
||||
|
||||
try:
|
||||
LOG.debug('starting the wamp client')
|
||||
self.wamp_agent_client.call(ctx, full_topic,
|
||||
port_uuid=p,
|
||||
tcp_port=r_tcp_port)
|
||||
|
||||
try:
|
||||
LOG.info('Updating the DB')
|
||||
VIF = str("iotronic" + str(r_tcp_port))
|
||||
port_iotronic.VIF_name = VIF
|
||||
port_iotronic.uuid = port['port']['id']
|
||||
port_iotronic.MAC_add = port['port']['mac_address']
|
||||
port_iotronic.board_uuid = str(board_uuid)
|
||||
port_iotronic.network = \
|
||||
port['port']['fixed_ips'][0]['subnet_id']
|
||||
port_iotronic.ip = \
|
||||
port['port']['fixed_ips'][0]['ip_address']
|
||||
port_iotronic.create()
|
||||
|
||||
try:
|
||||
LOG.debug('Configuration of the VIF')
|
||||
self.execute_on_board(ctx, board_uuid,
|
||||
"Configure_VIF",
|
||||
(port_iotronic, slash,))
|
||||
return port_iotronic
|
||||
|
||||
except Exception:
|
||||
LOG.error("Error while configuring the VIF")
|
||||
|
||||
except Exception as e:
|
||||
LOG.error('Error while updating the DB :' + str(e))
|
||||
|
||||
except Exception:
|
||||
LOG.error('wamp client error')
|
||||
|
||||
except Exception:
|
||||
LOG.error('Error while creating the VIF')
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(str(e))
|
||||
|
||||
def remove_VIF_from_board(self, ctx, board_uuid, port_uuid):
|
||||
|
||||
LOG.info('removing the port %s from board %s',
|
||||
port_uuid, board_uuid)
|
||||
|
||||
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
||||
port = objects.Port.get_by_uuid(ctx, port_uuid)
|
||||
VIF_name = str(port.VIF_name)
|
||||
|
||||
try:
|
||||
|
||||
self.execute_on_board(ctx, board_uuid, "Remove_VIF", (VIF_name,))
|
||||
port_num = int(VIF_name[8:])
|
||||
global Port
|
||||
Port.remove(port_num)
|
||||
try:
|
||||
LOG.info("Removing the port from Neutron "
|
||||
"and Iotronic databases")
|
||||
neutron.delete_port(board.agent, port_uuid)
|
||||
LOG.info("Port removed from Neutron DB")
|
||||
port.destroy()
|
||||
LOG.info("Port removed from Iotronic DB")
|
||||
|
||||
return port
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(str(e))
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(str(e))
|
||||
|
@ -276,3 +276,36 @@ class ConductorAPI(object):
|
||||
|
||||
return cctxt.call(context, 'restore_services_on_board',
|
||||
board_uuid=board_uuid)
|
||||
|
||||
def create_port_on_board(self, context, board_uuid, network,
|
||||
subnet, sec_groups, topic=None):
|
||||
"""Add a port on a Board
|
||||
|
||||
:param context: request context.
|
||||
:param board_uuid: the uuid of the board.
|
||||
:param network: the network uuid where the port will be created.
|
||||
:param subnet: the subnet uuid where the port will be created.
|
||||
:param sec_groups: security groups associated to the port.
|
||||
:param topic: RPC topic. Defaults to self.topic.
|
||||
:returns: created port object
|
||||
|
||||
"""
|
||||
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
|
||||
return cctxt.call(context, 'create_port_on_board',
|
||||
board_uuid=board_uuid, network_uuid=network,
|
||||
subnet_uuid=subnet, security_groups=sec_groups)
|
||||
|
||||
def remove_port_from_board(self, context, board_uuid,
|
||||
port_uuid, topic=None):
|
||||
"""remove a port from a Board
|
||||
|
||||
:param context: request context.
|
||||
:param board_uuid: the board uuid where the port resides.
|
||||
:param port_uuid: the UUID of the port.
|
||||
:returns: delete port object
|
||||
|
||||
"""
|
||||
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
|
||||
return cctxt.call(context, 'remove_VIF_from_board',
|
||||
board_uuid=board_uuid,
|
||||
port_uuid=port_uuid)
|
||||
|
@ -527,3 +527,51 @@ class Connection(object):
|
||||
:param board_uuid: The id or uuid of a service.
|
||||
:returns: A list of ExposedServices on the board.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_port_by_id(self, port_id):
|
||||
"""Return a port using the id
|
||||
|
||||
:param port_uuid: The id of a port.
|
||||
:returns: A port
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_port_by_uuid(self, port_uuid):
|
||||
"""Return a port using the uuid
|
||||
|
||||
:param port_uuid: The uuid of a port.
|
||||
:returns: A port
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_port_by_name(self, port_name):
|
||||
"""Return a port using the name of the port
|
||||
|
||||
:param port_name: The name of a port.
|
||||
:returns: A port
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_ports_by_board_uuid(self, board_uuid):
|
||||
"""Return a list of port on a board
|
||||
|
||||
:param board_uuid: The uuid of a board.
|
||||
:returns: A list of ports on a board
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_port(self, values):
|
||||
"""Create a new port.
|
||||
|
||||
:param values: A dict containing several items used to identify
|
||||
and track the service
|
||||
:returns: A port.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def destroy_port(self, port_uuid):
|
||||
"""Destroy a port.
|
||||
|
||||
:param port_uuid: The uuid of a port.
|
||||
"""
|
||||
|
@ -179,6 +179,27 @@ class Connection(api.Connection):
|
||||
|
||||
return query
|
||||
|
||||
def _add_ports_filters(self, query, filters):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
if 'board_uuid' in filters:
|
||||
query = query.\
|
||||
filter(models.Port.board_uuid == filters['board_uuid'])
|
||||
# if 'uuid' in filters:
|
||||
# query = query.filter(models.Port.uuid == filters['uuid'])
|
||||
# if 'with_public' in filters and filters['with_public']:
|
||||
# query = query.filter(
|
||||
# or_(
|
||||
# models.Port.owner == filters['owner'],
|
||||
# models.Port.public == 1)
|
||||
# )
|
||||
# else:
|
||||
# query = query.filter(models.Port.owner == filters['owner'])
|
||||
# elif 'public' in filters and filters['public']:
|
||||
# query = query.filter(models.Port.public == 1)
|
||||
print (str(query))
|
||||
|
||||
def _do_update_board(self, board_id, values):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
@ -294,8 +315,10 @@ class Connection(api.Connection):
|
||||
except NoResultFound:
|
||||
raise exception.BoardNotFound(board=board_code)
|
||||
|
||||
def destroy_board(self, board_id):
|
||||
# def get_board_by_port_uuid(self, port_uuid):
|
||||
# query = model_query(models.Port).filter_by(uuid=port_uuid)
|
||||
|
||||
def destroy_board(self, board_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Board, session=session)
|
||||
@ -838,7 +861,69 @@ class Connection(api.Connection):
|
||||
try:
|
||||
ref = query.with_lockmode('update').one()
|
||||
except NoResultFound:
|
||||
raise exception.ServiceNotFoundNotFound(uuid=service_id)
|
||||
raise exception.ServiceNotFound(uuid=service_id)
|
||||
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
||||
def get_port_by_id(self, port_id):
|
||||
query = model_query(models.Port).filter_by(id=port_id)
|
||||
try:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.PortNotFound(id=port_id)
|
||||
|
||||
def get_port_by_uuid(self, port_uuid):
|
||||
query = model_query(models.Port).filter_by(uuid=port_uuid)
|
||||
try:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.PortNotFound(uuid=port_uuid)
|
||||
|
||||
def get_port_by_name(self, port_name):
|
||||
query = model_query(models.Port).filter_by(name=port_name)
|
||||
try:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.PortNotFound(name=port_name)
|
||||
|
||||
def get_ports_by_board_uuid(self, board_uuid):
|
||||
query = model_query(
|
||||
models.Port).filter_by(
|
||||
board_uuid=board_uuid)
|
||||
try:
|
||||
return query.all()
|
||||
except NoResultFound:
|
||||
raise exception.NoPorts(uuid=board_uuid)
|
||||
|
||||
def get_ports_by_wamp_agent_id(self, wamp_agent_id):
|
||||
query = model_query(
|
||||
models.Port).filter_by(
|
||||
wamp_agent_id=wamp_agent_id)
|
||||
try:
|
||||
return query.all()
|
||||
except NoResultFound:
|
||||
raise exception.NoPortsManaged(wamp_agent_id=wamp_agent_id)
|
||||
|
||||
def get_port_list(
|
||||
self, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
query = model_query(models.Port)
|
||||
query = self._add_ports_filters(query, filters)
|
||||
return _paginate_query(models.Port, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def create_port(self, values):
|
||||
port = models.Port()
|
||||
port.update(values)
|
||||
port.save()
|
||||
return port
|
||||
|
||||
def destroy_port(self, uuid):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
query = model_query(models.Port, session=session)
|
||||
query = add_identity_filter(query, uuid)
|
||||
count = query.delete()
|
||||
if count == 0:
|
||||
raise exception.PortNotFound(uuid=uuid)
|
||||
|
@ -248,3 +248,23 @@ class ExposedService(Base):
|
||||
board_uuid = Column(String(36), ForeignKey('boards.uuid'))
|
||||
service_uuid = Column(String(36), ForeignKey('services.uuid'))
|
||||
public_port = Column(Integer)
|
||||
|
||||
|
||||
class Port(Base):
|
||||
"""Represents a port on board."""
|
||||
|
||||
__tablename__ = 'ports_on_boards'
|
||||
# __table_args__ = (
|
||||
# schema.UniqueConstraint('port_uuid', name='uniq_ports0uuid'),
|
||||
# table_args()
|
||||
# )
|
||||
id = Column(Integer, primary_key=True)
|
||||
board_uuid = Column(String(40), ForeignKey('boards.uuid'))
|
||||
uuid = Column(String(40))
|
||||
VIF_name = Column(String(30))
|
||||
# project = Column(String(36))
|
||||
MAC_add = Column(String(32))
|
||||
ip = Column(String(36))
|
||||
# status = Column(String(36))
|
||||
network = Column(String(36))
|
||||
# security_groups = Column(String(40))
|
||||
|
@ -18,6 +18,7 @@ from iotronic.objects import exposedservice
|
||||
from iotronic.objects import injectionplugin
|
||||
from iotronic.objects import location
|
||||
from iotronic.objects import plugin
|
||||
from iotronic.objects import port
|
||||
from iotronic.objects import service
|
||||
from iotronic.objects import sessionwp
|
||||
from iotronic.objects import wampagent
|
||||
@ -31,6 +32,7 @@ ExposedService = exposedservice.ExposedService
|
||||
SessionWP = sessionwp.SessionWP
|
||||
WampAgent = wampagent.WampAgent
|
||||
Service = service.Service
|
||||
Port = port.Port
|
||||
|
||||
__all__ = (
|
||||
Conductor,
|
||||
@ -41,5 +43,6 @@ __all__ = (
|
||||
Service,
|
||||
Plugin,
|
||||
InjectionPlugin,
|
||||
ExposedService
|
||||
ExposedService,
|
||||
Port
|
||||
)
|
||||
|
202
iotronic/objects/port.py
Normal file
202
iotronic/objects/port.py
Normal file
@ -0,0 +1,202 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
#
|
||||
# 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 oslo_utils import strutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from iotronic.common import exception
|
||||
from iotronic.db import api as db_api
|
||||
from iotronic.objects import base
|
||||
from iotronic.objects import utils as obj_utils
|
||||
|
||||
|
||||
class Port(base.IotronicObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'VIF_name': obj_utils.str_or_none,
|
||||
'network': obj_utils.str_or_none,
|
||||
'MAC_add': obj_utils.str_or_none,
|
||||
'ip': obj_utils.str_or_none,
|
||||
'board_uuid': obj_utils.str_or_none,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(port, db_port):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in port.fields:
|
||||
port[field] = db_port[field]
|
||||
port.obj_reset_changes()
|
||||
return port
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, port_id):
|
||||
"""Find a port based on its id or uuid and return a Port object.
|
||||
|
||||
:param port_id: the id *or* uuid of a port.
|
||||
:returns: a :class:`Port` object.
|
||||
"""
|
||||
if strutils.is_int_like(port_id):
|
||||
return cls.get_by_id(context, port_id)
|
||||
elif uuidutils.is_uuid_like(port_id):
|
||||
return cls.get_by_uuid(context, port_id)
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=port_id)
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, port_id):
|
||||
"""Find a port based on its integer id and return a Port object.
|
||||
|
||||
:param port_id: the id of a port.
|
||||
:returns: a :class:`Port` object.
|
||||
"""
|
||||
db_port = cls.dbapi.get_port_by_id(port_id)
|
||||
port = Port._from_db_object(cls(context), db_port)
|
||||
return port
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, port_uuid):
|
||||
"""Find a port based on uuid and return a Port object.
|
||||
|
||||
:param uuid: the uuid of a port.
|
||||
:returns: a :class:`Port` object.
|
||||
"""
|
||||
db_port = cls.dbapi.get_port_by_uuid(port_uuid)
|
||||
port = Port._from_db_object(cls(context), db_port)
|
||||
return port
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_by_name(cls, context, name):
|
||||
"""Find a port based on name and return a Port object.
|
||||
|
||||
:param name: the logical name of a port.
|
||||
:returns: a :class:`Port` object.
|
||||
"""
|
||||
db_port = cls.dbapi.get_port_by_name(name)
|
||||
port = Port._from_db_object(cls(context), db_port)
|
||||
return port
|
||||
|
||||
@base.remotable_classmethod
|
||||
def get_by_board_uuid(cls, context, board_uuid):
|
||||
"""Return a list of port objects.
|
||||
|
||||
:param context: Security context.
|
||||
:param board_uuid: The uuid of the board.
|
||||
|
||||
"""
|
||||
db_port = cls.dbapi.get_ports_by_board_uuid(board_uuid)
|
||||
return [Port._from_db_object(cls(context), obj)
|
||||
for obj in db_port]
|
||||
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, filters=None):
|
||||
"""Return a list of Port objects.
|
||||
|
||||
:param context: Security context.
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param sort_key: column to sort results by.
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:param filters: Filters to apply.
|
||||
:returns: a list of :class:`Port` object.
|
||||
|
||||
"""
|
||||
db_ports = cls.dbapi.get_port_list(filters=filters,
|
||||
limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return [Port._from_db_object(cls(context), obj)
|
||||
for obj in db_ports]
|
||||
|
||||
@base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a Port record in the DB.
|
||||
|
||||
Column-wise updates will be made based on the result of
|
||||
self.what_changed(). If target_power_state is provided,
|
||||
it will be checked against the in-database copy of the
|
||||
service before updates are made.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Port(context)
|
||||
|
||||
"""
|
||||
|
||||
values = self.obj_get_changes()
|
||||
db_port = self.dbapi.create_port(values)
|
||||
self._from_db_object(self, db_port)
|
||||
|
||||
@base.remotable
|
||||
def destroy(self, context=None):
|
||||
"""Delete the Port from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Port(context)
|
||||
"""
|
||||
self.dbapi.destroy_port(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def save(self, context=None):
|
||||
"""Save updates to this Port.
|
||||
|
||||
Column-wise updates will be made based on the result of
|
||||
self.what_changed(). If target_power_state is provided,
|
||||
it will be checked against the in-database copy of the
|
||||
service before updates are made.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Port(context)
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
self.dbapi.update_port(self.uuid, updates)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Refresh the object by re-fetching from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Port(context)
|
||||
"""
|
||||
current = self.__class__.get_by_uuid(self._context, self.uuid)
|
||||
for field in self.fields:
|
||||
if (hasattr(
|
||||
self, base.get_attrname(field))
|
||||
and self[field] != current[field]):
|
||||
self[field] = current[field]
|
@ -14,6 +14,8 @@
|
||||
# under the License.
|
||||
|
||||
import asyncio
|
||||
import subprocess
|
||||
import time
|
||||
import txaio
|
||||
|
||||
from iotronic.common import exception
|
||||
@ -74,8 +76,12 @@ async def wamp_request(kwarg):
|
||||
# OSLO ENDPOINT
|
||||
class WampEndpoint(object):
|
||||
def __init__(self, agent_uuid):
|
||||
|
||||
setattr(self, agent_uuid + '.s4t_invoke_wamp', self.s4t_invoke_wamp)
|
||||
|
||||
setattr(self, agent_uuid + '.create_tap_interface',
|
||||
self.create_tap_interface)
|
||||
|
||||
def s4t_invoke_wamp(self, ctx, **kwarg):
|
||||
LOG.debug("CONDUCTOR sent me: " + kwarg['wamp_rpc_call'])
|
||||
|
||||
@ -83,6 +89,16 @@ class WampEndpoint(object):
|
||||
|
||||
return r.result()
|
||||
|
||||
def create_tap_interface(self, ctx, port_uuid, tcp_port):
|
||||
time.sleep(12)
|
||||
LOG.debug('Creating tap interface on the wamp agent host')
|
||||
p = subprocess.Popen('socat -d -d TCP:localhost:' + tcp_port +
|
||||
',reuseaddr,forever,interval=10 TUN,tun-type=tap,'
|
||||
'tun-name=tap' + port_uuid[0:14] +
|
||||
',up ', shell=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
return 1
|
||||
|
||||
|
||||
class RPCServer(Thread):
|
||||
def __init__(self):
|
||||
@ -96,19 +112,30 @@ class RPCServer(Thread):
|
||||
target = oslo_messaging.Target(topic=AGENT_HOST + '.s4t_invoke_wamp',
|
||||
server='server1')
|
||||
|
||||
target1 = oslo_messaging.Target(topic=AGENT_HOST +
|
||||
'.create_tap_interface',
|
||||
server='server1')
|
||||
|
||||
access_policy = dispatcher.DefaultRPCAccessPolicy
|
||||
self.server = oslo_messaging.get_rpc_server(
|
||||
transport, target,
|
||||
endpoints, executor='threading',
|
||||
access_policy=access_policy)
|
||||
|
||||
self.server1 = oslo_messaging.get_rpc_server(
|
||||
transport, target1,
|
||||
endpoints, executor='threading',
|
||||
access_policy=access_policy)
|
||||
|
||||
def run(self):
|
||||
LOG.info("Starting AMQP server... ")
|
||||
self.server.start()
|
||||
self.server1.start()
|
||||
|
||||
def stop(self):
|
||||
LOG.info("Stopping AMQP server... ")
|
||||
self.server.stop()
|
||||
self.server1.stop()
|
||||
LOG.info("AMQP server stopped. ")
|
||||
|
||||
|
||||
|
@ -208,6 +208,28 @@ ENGINE = InnoDB
|
||||
AUTO_INCREMENT = 132
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
|
||||
----------------------------------
|
||||
-- Table `iotronic`.`ports_on_boards`
|
||||
----------------------------------
|
||||
|
||||
DROP TABLE IF EXISTS `ports_on_boards`;
|
||||
|
||||
CREATE TABLE `ports_on_boards` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) NOT NULL,
|
||||
`board_uuid` varchar(40) DEFAULT NULL,
|
||||
`MAC_add` varchar(32) DEFAULT NULL,
|
||||
`created_at` DATETIME DEFAULT NULL,
|
||||
`updated_at` DATETIME DEFAULT NULL,
|
||||
`VIF_name` varchar(30) DEFAULT NULL,
|
||||
`network` varchar(36) NOT NULL,
|
||||
`ip` varchar(36) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `p_board_uuid` (`board_uuid`),
|
||||
CONSTRAINT `p_board_uuid` FOREIGN KEY (`board_uuid`) REFERENCES `boards` (`uuid`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=417 DEFAULT CHARSET=utf8;
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `iotronic`.`exposed_services`
|
||||
-- -----------------------------------------------------
|
||||
|
Loading…
x
Reference in New Issue
Block a user