Add network-create to server side
Add network-create to server side, so that zun have the ability to mange the network based on the kuryr driver. Change-Id: Ifdd8804280bc015d93888efc77afecc72ec77cff
This commit is contained in:
parent
c714b11fdd
commit
5a43580a70
@ -28,6 +28,7 @@ from zun.api.controllers.v1 import capsules as capsule_controller
|
|||||||
from zun.api.controllers.v1 import containers as container_controller
|
from zun.api.controllers.v1 import containers as container_controller
|
||||||
from zun.api.controllers.v1 import hosts as host_controller
|
from zun.api.controllers.v1 import hosts as host_controller
|
||||||
from zun.api.controllers.v1 import images as image_controller
|
from zun.api.controllers.v1 import images as image_controller
|
||||||
|
from zun.api.controllers.v1 import networks as network_controller
|
||||||
from zun.api.controllers.v1 import zun_services
|
from zun.api.controllers.v1 import zun_services
|
||||||
from zun.api.controllers import versions as ver
|
from zun.api.controllers import versions as ver
|
||||||
from zun.api import http_error
|
from zun.api import http_error
|
||||||
@ -67,6 +68,7 @@ class V1(controllers_base.APIBase):
|
|||||||
'services',
|
'services',
|
||||||
'containers',
|
'containers',
|
||||||
'images',
|
'images',
|
||||||
|
'networks',
|
||||||
'hosts',
|
'hosts',
|
||||||
'capsules',
|
'capsules',
|
||||||
'availability_zones'
|
'availability_zones'
|
||||||
@ -103,6 +105,12 @@ class V1(controllers_base.APIBase):
|
|||||||
pecan.request.host_url,
|
pecan.request.host_url,
|
||||||
'images', '',
|
'images', '',
|
||||||
bookmark=True)]
|
bookmark=True)]
|
||||||
|
v1.networks = [link.make_link('self', pecan.request.host_url,
|
||||||
|
'networks', ''),
|
||||||
|
link.make_link('bookmark',
|
||||||
|
pecan.request.host_url,
|
||||||
|
'networks', '',
|
||||||
|
bookmark=True)]
|
||||||
v1.hosts = [link.make_link('self', pecan.request.host_url,
|
v1.hosts = [link.make_link('self', pecan.request.host_url,
|
||||||
'hosts', ''),
|
'hosts', ''),
|
||||||
link.make_link('bookmark',
|
link.make_link('bookmark',
|
||||||
@ -130,6 +138,7 @@ class Controller(controllers_base.Controller):
|
|||||||
services = zun_services.ZunServiceController()
|
services = zun_services.ZunServiceController()
|
||||||
containers = container_controller.ContainersController()
|
containers = container_controller.ContainersController()
|
||||||
images = image_controller.ImagesController()
|
images = image_controller.ImagesController()
|
||||||
|
networks = network_controller.NetworkController()
|
||||||
hosts = host_controller.HostController()
|
hosts = host_controller.HostController()
|
||||||
availability_zones = a_zone.AvailabilityZoneController()
|
availability_zones = a_zone.AvailabilityZoneController()
|
||||||
capsules = capsule_controller.CapsuleController()
|
capsules = capsule_controller.CapsuleController()
|
||||||
|
72
zun/api/controllers/v1/networks.py
Normal file
72
zun/api/controllers/v1/networks.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# 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_log import log as logging
|
||||||
|
import pecan
|
||||||
|
|
||||||
|
from zun.api.controllers import base
|
||||||
|
from zun.api.controllers.v1 import collection
|
||||||
|
from zun.api.controllers.v1.schemas import network as schema
|
||||||
|
from zun.api.controllers.v1.views import network_view as view
|
||||||
|
from zun.api import utils as api_utils
|
||||||
|
from zun.api import validation
|
||||||
|
from zun.common import exception
|
||||||
|
from zun.common import policy
|
||||||
|
from zun import objects
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkCollection(collection.Collection):
|
||||||
|
"""API representation of a collection of network."""
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'network'
|
||||||
|
}
|
||||||
|
|
||||||
|
"""A list containing network objects"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(NetworkCollection, self).__init__(**kwargs)
|
||||||
|
self._type = 'network'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_with_links(rpc_network, limit, url=None,
|
||||||
|
expand=False, **kwargs):
|
||||||
|
collection = NetworkCollection()
|
||||||
|
collection.network = [view.format_network(url, p) for p in rpc_network]
|
||||||
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||||
|
return collection
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkController(base.Controller):
|
||||||
|
"""Controller for Network"""
|
||||||
|
|
||||||
|
@pecan.expose('json')
|
||||||
|
@api_utils.enforce_content_types(['application/json'])
|
||||||
|
@exception.wrap_pecan_controller_exception
|
||||||
|
@validation.validated(schema.network_create)
|
||||||
|
def post(self, **network_dict):
|
||||||
|
"""Create a new network.
|
||||||
|
|
||||||
|
:param network_dict: a network within the request body.
|
||||||
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, "network:create",
|
||||||
|
action="network:create")
|
||||||
|
network_dict['project_id'] = context.project_id
|
||||||
|
network_dict['user_id'] = context.user_id
|
||||||
|
new_network = objects.Network(context, **network_dict)
|
||||||
|
new_network.create(context)
|
||||||
|
pecan.request.compute_api.network_create(context, new_network)
|
||||||
|
pecan.response.status = 202
|
||||||
|
return view.format_network(pecan.request.host_url, new_network)
|
25
zun/api/controllers/v1/schemas/network.py
Normal file
25
zun/api/controllers/v1/schemas/network.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# 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 zun.api.validation import parameter_types
|
||||||
|
|
||||||
|
_network_properties = {
|
||||||
|
'neutron_net_id': parameter_types.neutron_net_id,
|
||||||
|
'name': parameter_types.network_name
|
||||||
|
}
|
||||||
|
|
||||||
|
network_create = {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': _network_properties,
|
||||||
|
'required': ['name'],
|
||||||
|
'additionalProperties': False
|
||||||
|
}
|
34
zun/api/controllers/v1/views/network_view.py
Normal file
34
zun/api/controllers/v1/views/network_view.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
_basic_keys = (
|
||||||
|
'uuid',
|
||||||
|
'name',
|
||||||
|
'project_id',
|
||||||
|
'user_id',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def format_network(url, network):
|
||||||
|
def transform(key, value):
|
||||||
|
if key not in _basic_keys:
|
||||||
|
return
|
||||||
|
if key == 'uuid':
|
||||||
|
yield ('uuid', value)
|
||||||
|
else:
|
||||||
|
yield (key, value)
|
||||||
|
|
||||||
|
return dict(itertools.chain.from_iterable(
|
||||||
|
transform(k, v) for k, v in network.as_dict().items()))
|
@ -481,3 +481,17 @@ capsule_template = {
|
|||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
"required": ['kind', 'spec', 'metadata']
|
"required": ['kind', 'spec', 'metadata']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
neutron_net_id = {
|
||||||
|
'type': ['string', 'null'],
|
||||||
|
'minLength': 2,
|
||||||
|
'maxLength': 255,
|
||||||
|
'pattern': '[a-zA-Z0-9][a-zA-Z0-9_.-]'
|
||||||
|
}
|
||||||
|
|
||||||
|
network_name = {
|
||||||
|
'type': ['string', 'null'],
|
||||||
|
'minLength': 2,
|
||||||
|
'maxLength': 255,
|
||||||
|
'pattern': '[a-zA-Z0-9][a-zA-Z0-9_.-]'
|
||||||
|
}
|
||||||
|
@ -18,12 +18,12 @@ NETWORK = 'network:%s'
|
|||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=NETWORK % 'attach_external_network',
|
name=NETWORK % 'create',
|
||||||
check_str=base.ROLE_ADMIN,
|
check_str=base.ROLE_ADMIN,
|
||||||
description='Attach an unshared external network to a container',
|
description='Create a network',
|
||||||
operations=[
|
operations=[
|
||||||
{
|
{
|
||||||
'path': '/v1/containers',
|
'path': '/v1/networks',
|
||||||
'method': 'POST'
|
'method': 'POST'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -225,3 +225,6 @@ class API(object):
|
|||||||
self._record_action_start(context, container,
|
self._record_action_start(context, container,
|
||||||
container_actions.NETWORK_ATTACH)
|
container_actions.NETWORK_ATTACH)
|
||||||
return self.rpcapi.network_attach(context, container, *args)
|
return self.rpcapi.network_attach(context, container, *args)
|
||||||
|
|
||||||
|
def network_create(self, context, network):
|
||||||
|
return self.rpcapi.network_create(context, network)
|
||||||
|
@ -1194,3 +1194,23 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
consts.NETWORK_ATTACHING)
|
consts.NETWORK_ATTACHING)
|
||||||
self.driver.network_attach(context, container, network)
|
self.driver.network_attach(context, container, network)
|
||||||
self._update_task_state(context, container, None)
|
self._update_task_state(context, container, None)
|
||||||
|
|
||||||
|
def network_create(self, context, network):
|
||||||
|
utils.spawn_n(self._do_create_network, context, network)
|
||||||
|
|
||||||
|
def _do_create_network(self, context, network):
|
||||||
|
LOG.debug('Create network')
|
||||||
|
try:
|
||||||
|
docker_network = self.driver.create_network(context, network)
|
||||||
|
network.network_id = docker_network['Id']
|
||||||
|
network.save()
|
||||||
|
except exception.NetworkNotFound as e:
|
||||||
|
LOG.error(six.text_type(e))
|
||||||
|
return
|
||||||
|
except exception.DockerError as e:
|
||||||
|
LOG.error("Error occurred while calling Docker network API: %s",
|
||||||
|
six.text_type(e))
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
||||||
|
raise
|
||||||
|
@ -202,3 +202,7 @@ class API(rpc_service.API):
|
|||||||
def network_attach(self, context, container, network):
|
def network_attach(self, context, container, network):
|
||||||
return self._call(container.host, 'network_attach',
|
return self._call(container.host, 'network_attach',
|
||||||
container=container, network=network)
|
container=container, network=network)
|
||||||
|
|
||||||
|
def network_create(self, context, new_network):
|
||||||
|
host = None
|
||||||
|
return self._cast(host, 'network_create', network=new_network)
|
||||||
|
@ -1123,3 +1123,15 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
addresses.update(update)
|
addresses.update(update)
|
||||||
container.addresses = addresses
|
container.addresses = addresses
|
||||||
container.save(context)
|
container.save(context)
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
network_api = zun_network.api(context,
|
||||||
|
docker_api=docker)
|
||||||
|
docker_net_name = self._get_docker_network_name(
|
||||||
|
context, network.neutron_net_id)
|
||||||
|
docker_network = network_api.create_network(
|
||||||
|
neutron_net_id=network.neutron_net_id,
|
||||||
|
name=docker_net_name
|
||||||
|
)
|
||||||
|
return docker_network
|
||||||
|
@ -261,6 +261,12 @@ class ContainerDriver(object):
|
|||||||
def network_attach(self, context, container, network):
|
def network_attach(self, context, container, network):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def inspect_network(self, network):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def node_support_disk_quota(self):
|
def node_support_disk_quota(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -972,3 +972,48 @@ def quota_class_update(context, class_name, resource, limit):
|
|||||||
"""Update a quota class or raise if it does not exist"""
|
"""Update a quota class or raise if it does not exist"""
|
||||||
return _get_dbdriver_instance().quota_class_update(context, class_name,
|
return _get_dbdriver_instance().quota_class_update(context, class_name,
|
||||||
resource, limit)
|
resource, limit)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def create_network(context, values):
|
||||||
|
"""Create a new network.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param values: A dict containing several items used to identify
|
||||||
|
and track the network, and several dicts which are
|
||||||
|
passed
|
||||||
|
into the Drivers when managing this network. For
|
||||||
|
example:
|
||||||
|
::
|
||||||
|
{
|
||||||
|
'uuid': uuidutils.generate_uuid(),
|
||||||
|
'name': 'example',
|
||||||
|
'type': 'virt'
|
||||||
|
}
|
||||||
|
:returns: A network.
|
||||||
|
"""
|
||||||
|
return _get_dbdriver_instance().create_network(context, values)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def get_network_by_uuid(context, network_uuid):
|
||||||
|
"""Return a network.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param network_uuid: The uuid of a network.
|
||||||
|
:returns: A network.
|
||||||
|
"""
|
||||||
|
return _get_dbdriver_instance().get_network_by_uuid(context, network_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def update_network(context, uuid, values):
|
||||||
|
"""Update properties of a network.
|
||||||
|
|
||||||
|
:param context: Request context
|
||||||
|
:param uuid: The id or uuid of a network.
|
||||||
|
:param values: The properties to be updated
|
||||||
|
:returns: A network.
|
||||||
|
"""
|
||||||
|
return _get_dbdriver_instance().update_network(
|
||||||
|
context, uuid, values)
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 3298c6a5c3d9
|
||||||
|
Revises: 271c7f45982d
|
||||||
|
Create Date: 2018-04-28 06:32:26.493248
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '3298c6a5c3d9'
|
||||||
|
down_revision = '271c7f45982d'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table(
|
||||||
|
'network',
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('project_id', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('user_id', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('network_id', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('neutron_net_id', sa.String(length=255), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('uuid', name='uniq_network0uuid'),
|
||||||
|
sa.UniqueConstraint('neutron_net_id',
|
||||||
|
name='uniq_network0neutron_net_id'),
|
||||||
|
mysql_charset='utf8',
|
||||||
|
mysql_engine='InnoDB'
|
||||||
|
)
|
@ -1157,3 +1157,49 @@ class Connection(object):
|
|||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
raise exception.QuotaClassNotFound(class_name=class_name)
|
raise exception.QuotaClassNotFound(class_name=class_name)
|
||||||
|
|
||||||
|
def create_network(self, context, values):
|
||||||
|
# ensure defaults are present for new containers
|
||||||
|
if not values.get('uuid'):
|
||||||
|
values['uuid'] = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
if values.get('name'):
|
||||||
|
self._validate_unique_container_name(context, values['name'])
|
||||||
|
|
||||||
|
network = models.Network()
|
||||||
|
network.update(values)
|
||||||
|
try:
|
||||||
|
network.save()
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exception.ContainerAlreadyExists(field='UUID',
|
||||||
|
value=values['uuid'])
|
||||||
|
return network
|
||||||
|
|
||||||
|
def update_network(self, context, network_uuid, values):
|
||||||
|
# NOTE(dtantsur): this can lead to very strange errors
|
||||||
|
if 'uuid' in values:
|
||||||
|
msg = _("Cannot overwrite UUID for an existing docker network.")
|
||||||
|
raise exception.InvalidParameterValue(err=msg)
|
||||||
|
return self._do_update_network(network_uuid, values)
|
||||||
|
|
||||||
|
def _do_update_network(self, network_uuid, values):
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
query = model_query(models.Network, session=session)
|
||||||
|
query = add_identity_filter(query, network_uuid)
|
||||||
|
try:
|
||||||
|
ref = query.with_lockmode('update').one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise exception.NetworkNotFound(network=network_uuid)
|
||||||
|
|
||||||
|
ref.update(values)
|
||||||
|
return ref
|
||||||
|
|
||||||
|
def get_network_by_uuid(self, context, network_uuid):
|
||||||
|
query = model_query(models.Network)
|
||||||
|
query = self._add_project_filters(context, query)
|
||||||
|
query = query.filter_by(uuid=network_uuid)
|
||||||
|
try:
|
||||||
|
return query.one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise exception.NetworkNotFound(network=network_uuid)
|
||||||
|
@ -499,3 +499,22 @@ class QuotaClass(Base):
|
|||||||
class_name = Column(String(255))
|
class_name = Column(String(255))
|
||||||
resource = Column(String(255))
|
resource = Column(String(255))
|
||||||
hard_limit = Column(Integer)
|
hard_limit = Column(Integer)
|
||||||
|
|
||||||
|
|
||||||
|
class Network(Base):
|
||||||
|
"""Represents a network. """
|
||||||
|
|
||||||
|
__tablename__ = 'network'
|
||||||
|
__table_args__ = (
|
||||||
|
schema.UniqueConstraint('uuid', name='uniq_network0uuid'),
|
||||||
|
schema.UniqueConstraint('neutron_net_id',
|
||||||
|
name='uniq_network0neutron_net_id'),
|
||||||
|
table_args()
|
||||||
|
)
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String(255))
|
||||||
|
neutron_net_id = Column(String(255))
|
||||||
|
network_id = Column(String(255))
|
||||||
|
project_id = Column(String(255))
|
||||||
|
user_id = Column(String(255))
|
||||||
|
uuid = Column(String(36))
|
||||||
|
@ -16,6 +16,7 @@ from zun.objects import container
|
|||||||
from zun.objects import container_action
|
from zun.objects import container_action
|
||||||
from zun.objects import container_pci_requests
|
from zun.objects import container_pci_requests
|
||||||
from zun.objects import image
|
from zun.objects import image
|
||||||
|
from zun.objects import network
|
||||||
from zun.objects import numa
|
from zun.objects import numa
|
||||||
from zun.objects import pci_device
|
from zun.objects import pci_device
|
||||||
from zun.objects import pci_device_pool
|
from zun.objects import pci_device_pool
|
||||||
@ -31,6 +32,7 @@ Container = container.Container
|
|||||||
VolumeMapping = volume_mapping.VolumeMapping
|
VolumeMapping = volume_mapping.VolumeMapping
|
||||||
ZunService = zun_service.ZunService
|
ZunService = zun_service.ZunService
|
||||||
Image = image.Image
|
Image = image.Image
|
||||||
|
Network = network.Network
|
||||||
NUMANode = numa.NUMANode
|
NUMANode = numa.NUMANode
|
||||||
NUMATopology = numa.NUMATopology
|
NUMATopology = numa.NUMATopology
|
||||||
ResourceProvider = resource_provider.ResourceProvider
|
ResourceProvider = resource_provider.ResourceProvider
|
||||||
@ -51,6 +53,7 @@ __all__ = (
|
|||||||
'VolumeMapping',
|
'VolumeMapping',
|
||||||
'ZunService',
|
'ZunService',
|
||||||
'Image',
|
'Image',
|
||||||
|
'Network',
|
||||||
'ResourceProvider',
|
'ResourceProvider',
|
||||||
'ResourceClass',
|
'ResourceClass',
|
||||||
'NUMANode',
|
'NUMANode',
|
||||||
|
94
zun/objects/network.py
Normal file
94
zun/objects/network.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# 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_versionedobjects import fields
|
||||||
|
|
||||||
|
from zun.db import api as dbapi
|
||||||
|
from zun.objects import base
|
||||||
|
|
||||||
|
|
||||||
|
@base.ZunObjectRegistry.register
|
||||||
|
class Network(base.ZunPersistentObject, base.ZunObject):
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'uuid': fields.UUIDField(nullable=True),
|
||||||
|
'project_id': fields.StringField(nullable=True),
|
||||||
|
'user_id': fields.StringField(nullable=True),
|
||||||
|
'name': fields.StringField(nullable=True),
|
||||||
|
'network_id': fields.StringField(nullable=True),
|
||||||
|
'neutron_net_id': fields.StringField(nullable=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object(network, db_network):
|
||||||
|
"""Converts a database entity to a formal object."""
|
||||||
|
for field in network.fields:
|
||||||
|
setattr(network, field, db_network[field])
|
||||||
|
|
||||||
|
network.obj_reset_changes()
|
||||||
|
return network
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object_list(db_objects, cls, context):
|
||||||
|
"""Converts a list of database entities to a list of formal objects."""
|
||||||
|
return [Network._from_db_object(cls(context), obj)
|
||||||
|
for obj in db_objects]
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_uuid(cls, context, uuid):
|
||||||
|
"""Find an network based on uuid and return a :class:`Network` object.
|
||||||
|
|
||||||
|
:param uuid: the uuid of a network.
|
||||||
|
:param context: Security context
|
||||||
|
:returns: a :class:`Network` object.
|
||||||
|
"""
|
||||||
|
db_network = dbapi.get_network_by_uuid(context, uuid)
|
||||||
|
network = Network._from_db_object(cls(context), db_network)
|
||||||
|
return network
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def create(self, context):
|
||||||
|
"""Create a Network record in 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.: Network(context)
|
||||||
|
|
||||||
|
"""
|
||||||
|
values = self.obj_get_changes()
|
||||||
|
db_network = dbapi.create_network(context, values)
|
||||||
|
self._from_db_object(self, db_network)
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def save(self, context=None):
|
||||||
|
"""Save updates to this Network.
|
||||||
|
|
||||||
|
Updates will be made column by column based on the result
|
||||||
|
of self.what_changed().
|
||||||
|
|
||||||
|
: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.: Network(context)
|
||||||
|
"""
|
||||||
|
updates = self.obj_get_changes()
|
||||||
|
dbapi.update_network(context, self.uuid, updates)
|
||||||
|
|
||||||
|
self.obj_reset_changes()
|
@ -73,6 +73,10 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
'rel': 'self'},
|
'rel': 'self'},
|
||||||
{'href': 'http://localhost/images/',
|
{'href': 'http://localhost/images/',
|
||||||
'rel': 'bookmark'}],
|
'rel': 'bookmark'}],
|
||||||
|
'networks': [{'href': 'http://localhost/v1/networks/',
|
||||||
|
'rel': 'self'},
|
||||||
|
{'href': 'http://localhost/networks/',
|
||||||
|
'rel': 'bookmark'}],
|
||||||
'capsules': [{'href': 'http://localhost/v1/capsules/',
|
'capsules': [{'href': 'http://localhost/v1/capsules/',
|
||||||
'rel': 'self'},
|
'rel': 'self'},
|
||||||
{'href': 'http://localhost/capsules/',
|
{'href': 'http://localhost/capsules/',
|
||||||
|
29
zun/tests/unit/api/controllers/v1/test_networks.py
Normal file
29
zun/tests/unit/api/controllers/v1/test_networks.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 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 mock import patch
|
||||||
|
from zun.tests.unit.api import base as api_base
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkController(api_base.FunctionalTest):
|
||||||
|
@patch('zun.common.policy.enforce')
|
||||||
|
@patch('zun.compute.api.API.network_create')
|
||||||
|
def test_network_create(self, mock_network_create, mock_policy):
|
||||||
|
mock_policy.return_value = True
|
||||||
|
mock_network_create.side_effect = lambda x, y: y
|
||||||
|
params = ('{"name": "network-test", "neutron_net_id": "test-id"}')
|
||||||
|
response = self.post('/v1/networks/',
|
||||||
|
params=params,
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
self.assertEqual(202, response.status_int)
|
||||||
|
self.assertTrue(mock_network_create.called)
|
@ -34,6 +34,8 @@ class TestAPI(base.TestCase):
|
|||||||
self.compute_api = api.API(self.context)
|
self.compute_api = api.API(self.context)
|
||||||
self.container = objects.Container(
|
self.container = objects.Container(
|
||||||
self.context, **utils.get_test_container())
|
self.context, **utils.get_test_container())
|
||||||
|
self.network = objects.Network(
|
||||||
|
self.context, **utils.get_test_network())
|
||||||
|
|
||||||
@mock.patch('zun.compute.api.API._record_action_start')
|
@mock.patch('zun.compute.api.API._record_action_start')
|
||||||
@mock.patch('zun.compute.rpcapi.API.container_create')
|
@mock.patch('zun.compute.rpcapi.API.container_create')
|
||||||
@ -461,3 +463,9 @@ class TestAPI(base.TestCase):
|
|||||||
container_actions.NETWORK_DETACH, want_result=False)
|
container_actions.NETWORK_DETACH, want_result=False)
|
||||||
mock_call.assert_called_once_with(
|
mock_call.assert_called_once_with(
|
||||||
container.host, "network_detach", container=container, network={})
|
container.host, "network_detach", container=container, network={})
|
||||||
|
|
||||||
|
@mock.patch('zun.compute.rpcapi.API.network_create')
|
||||||
|
def test_network_create(self, mock_network_create):
|
||||||
|
network = self.network
|
||||||
|
self.compute_api.network_create(self.context, network)
|
||||||
|
self.assertTrue(mock_network_create.called)
|
||||||
|
@ -24,6 +24,7 @@ import zun.conf
|
|||||||
from zun.objects.container import Container
|
from zun.objects.container import Container
|
||||||
from zun.objects.container_action import ContainerActionEvent
|
from zun.objects.container_action import ContainerActionEvent
|
||||||
from zun.objects.image import Image
|
from zun.objects.image import Image
|
||||||
|
from zun.objects.network import Network
|
||||||
from zun.objects.volume_mapping import VolumeMapping
|
from zun.objects.volume_mapping import VolumeMapping
|
||||||
from zun.tests import base
|
from zun.tests import base
|
||||||
from zun.tests.unit.container.fake_driver import FakeDriver as fake_driver
|
from zun.tests.unit.container.fake_driver import FakeDriver as fake_driver
|
||||||
@ -1225,3 +1226,13 @@ class TestManager(base.TestCase):
|
|||||||
container)
|
container)
|
||||||
mock_is_volume_available.assert_called_once()
|
mock_is_volume_available.assert_called_once()
|
||||||
mock_fail.assert_not_called()
|
mock_fail.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch.object(Network, 'save')
|
||||||
|
@mock.patch.object(fake_driver, 'create_network')
|
||||||
|
def test_network_create(self, mock_create, mock_save):
|
||||||
|
network = Network(self.context, **utils.get_test_network())
|
||||||
|
ret = ({'Id': '0eeftestnetwork'})
|
||||||
|
mock_create.return_value = ret
|
||||||
|
self.compute_manager._do_create_network(self.context, network)
|
||||||
|
mock_create.assert_any_call(self.context, network)
|
||||||
|
mock_save.assert_called_once()
|
||||||
|
@ -565,3 +565,17 @@ def get_test_quota_class(**kwargs):
|
|||||||
setattr(fake_quota_class, k, v)
|
setattr(fake_quota_class, k, v)
|
||||||
|
|
||||||
return fake_quota_class
|
return fake_quota_class
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_network(**kwargs):
|
||||||
|
return {
|
||||||
|
'id': kwargs.get('id', 42),
|
||||||
|
'name': kwargs.get('name', 'fake_name'),
|
||||||
|
'uuid': kwargs.get('uuid', 'z2b96c5b-242a-41a0-a736-b6e1fada071b'),
|
||||||
|
'network_id': kwargs.get('network_id', '0eeftestnetwork'),
|
||||||
|
'project_id': kwargs.get('project_id', 'fake_project'),
|
||||||
|
'user_id': kwargs.get('user_id', 'fake_user'),
|
||||||
|
'created_at': kwargs.get('created_at'),
|
||||||
|
'updated_at': kwargs.get('updated_at'),
|
||||||
|
'neutron_net_id': kwargs.get('neutron_net_id', 'bar'),
|
||||||
|
}
|
||||||
|
55
zun/tests/unit/objects/test_network.py
Normal file
55
zun/tests/unit/objects/test_network.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Copyright 2015 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from zun import objects
|
||||||
|
from zun.tests.unit.db import base
|
||||||
|
from zun.tests.unit.db import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkObject(base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNetworkObject, self).setUp()
|
||||||
|
self.fake_network = utils.get_test_network()
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
with mock.patch.object(self.dbapi, 'create_network',
|
||||||
|
autospec=True) as mock_create_network:
|
||||||
|
mock_create_network.return_value = self.fake_network
|
||||||
|
network = objects.Network(self.context, **self.fake_network)
|
||||||
|
network.create(self.context)
|
||||||
|
mock_create_network.assert_called_once_with(self.context,
|
||||||
|
self.fake_network)
|
||||||
|
self.assertEqual(self.context, network._context)
|
||||||
|
|
||||||
|
def test_save(self):
|
||||||
|
uuid = self.fake_network['uuid']
|
||||||
|
with mock.patch.object(self.dbapi, 'get_network_by_uuid',
|
||||||
|
autospec=True) as mock_get_network:
|
||||||
|
mock_get_network.return_value = self.fake_network
|
||||||
|
with mock.patch.object(self.dbapi, 'update_network',
|
||||||
|
autospec=True) as mock_update_network:
|
||||||
|
network = objects.Network.get_by_uuid(self.context, uuid)
|
||||||
|
network.name = 'network-test'
|
||||||
|
network.neutron_net_id = 'test-id'
|
||||||
|
network.save()
|
||||||
|
mock_get_network.assert_called_once_with(self.context, uuid)
|
||||||
|
params = {'name': 'network-test', 'neutron_net_id': 'test-id'}
|
||||||
|
mock_update_network.assert_called_once_with(None,
|
||||||
|
uuid,
|
||||||
|
params)
|
||||||
|
self.assertEqual(self.context, network._context)
|
@ -363,7 +363,8 @@ object_data = {
|
|||||||
'ContainerPCIRequest': '1.0-b060f9f9f734bedde79a71a4d3112ee0',
|
'ContainerPCIRequest': '1.0-b060f9f9f734bedde79a71a4d3112ee0',
|
||||||
'ContainerPCIRequests': '1.0-7b8f7f044661fe4e24e6949c035af2c4',
|
'ContainerPCIRequests': '1.0-7b8f7f044661fe4e24e6949c035af2c4',
|
||||||
'ContainerAction': '1.1-b0c721f9e10c6c0d1e41e512c49eb877',
|
'ContainerAction': '1.1-b0c721f9e10c6c0d1e41e512c49eb877',
|
||||||
'ContainerActionEvent': '1.0-2974d0a6f5d4821fd4e223a88c10181a'
|
'ContainerActionEvent': '1.0-2974d0a6f5d4821fd4e223a88c10181a',
|
||||||
|
'Network': '1.0-235ba13359282107f27c251af9aaffcd',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user