Virtual-networks

Change-Id: Ia426970fbece3f0c18bdf0ee555c37407dfbe981
This commit is contained in:
Zakaria 2018-05-31 15:05:57 +02:00
parent 737883be4b
commit 67b15a6b7a
16 changed files with 1199 additions and 8 deletions

View File

@ -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

View File

@ -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):

View File

@ -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)

View 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

View File

@ -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.

View File

@ -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
View 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

View File

@ -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))

View File

@ -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)

View File

@ -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.
"""

View File

@ -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)

View File

@ -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))

View File

@ -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
View 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]

View File

@ -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. ")

View File

@ -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`
-- -----------------------------------------------------