API for managing network groups
Individual network groups can be created, deleted and modified. IP range management for each network is still handled by the standard network_configuration API handlers. Change-Id: I861485b5aebd8ae784fc71f405aab87e73f6d2e0 Implements: blueprint templates-for-networking
This commit is contained in:
parent
52a413e672
commit
7084177cae
38
nailgun/nailgun/api/v1/handlers/network_group.py
Normal file
38
nailgun/nailgun/api/v1/handlers/network_group.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 nailgun.api.v1.handlers.base import CollectionHandler
|
||||
from nailgun.api.v1.handlers.base import SingleHandler
|
||||
from nailgun.api.v1.validators.network import NetworkGroupValidator
|
||||
|
||||
from nailgun import objects
|
||||
|
||||
|
||||
class NetworkGroupHandler(SingleHandler):
|
||||
"""Network group handler
|
||||
"""
|
||||
|
||||
validator = NetworkGroupValidator
|
||||
single = objects.NetworkGroup
|
||||
|
||||
|
||||
class NetworkGroupCollectionHandler(CollectionHandler):
|
||||
"""Network group collection handler
|
||||
"""
|
||||
|
||||
collection = objects.NetworkGroupCollection
|
||||
validator = NetworkGroupValidator
|
@ -43,6 +43,8 @@ from nailgun.api.v1.handlers.logs import LogPackageHandler
|
||||
from nailgun.api.v1.handlers.logs import LogSourceByNodeCollectionHandler
|
||||
from nailgun.api.v1.handlers.logs import LogSourceCollectionHandler
|
||||
from nailgun.api.v1.handlers.logs import SnapshotDownloadHandler
|
||||
from nailgun.api.v1.handlers.network_group import NetworkGroupCollectionHandler
|
||||
from nailgun.api.v1.handlers.network_group import NetworkGroupHandler
|
||||
from nailgun.api.v1.handlers.node_group import NodeGroupCollectionHandler
|
||||
from nailgun.api.v1.handlers.node_group import NodeGroupHandler
|
||||
|
||||
@ -192,6 +194,11 @@ urls = (
|
||||
r'/clusters/(?P<obj_id>\d+)/deployment_tasks/?$',
|
||||
ClusterDeploymentTasksHandler,
|
||||
|
||||
r'/networks/?$',
|
||||
NetworkGroupCollectionHandler,
|
||||
r'/networks/(?P<obj_id>\d+)/?$',
|
||||
NetworkGroupHandler,
|
||||
|
||||
r'/clusters/(?P<cluster_id>\d+)/assignment/?$',
|
||||
NodeAssignmentHandler,
|
||||
r'/clusters/(?P<cluster_id>\d+)/unassignment/?$',
|
||||
|
@ -0,0 +1,33 @@
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 nailgun.api.v1.validators.json_schema import base_types
|
||||
|
||||
|
||||
single_scheme = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "NetworkGroup",
|
||||
"description": "Serialized NetworkGroup object",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "integer"},
|
||||
"name": {"type": "string"},
|
||||
"release": base_types.NULLABLE_ID,
|
||||
"vlan_start": {"type": "number"},
|
||||
"cidr": base_types.NET_ADDRESS,
|
||||
"gateway": base_types.NULLABLE_IP_ADDRESS,
|
||||
"group_id": base_types.NULLABLE_ID,
|
||||
"meta": {"type": "object"}
|
||||
}
|
||||
}
|
@ -13,7 +13,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
from nailgun.api.v1.validators.base import BasicValidator
|
||||
from nailgun.api.v1.validators.json_schema import network_group as ng_scheme
|
||||
from nailgun import consts
|
||||
|
||||
from nailgun import objects
|
||||
@ -22,7 +25,6 @@ from nailgun.db import db
|
||||
from nailgun.db.sqlalchemy.models import Cluster
|
||||
from nailgun.db.sqlalchemy.models import Node
|
||||
from nailgun.errors import errors
|
||||
import six
|
||||
|
||||
|
||||
class NetworkConfigurationValidator(BasicValidator):
|
||||
@ -377,3 +379,40 @@ class NetAssignmentValidator(BasicValidator):
|
||||
"bond".format(node['id'], iface['id']),
|
||||
log_message=True
|
||||
)
|
||||
|
||||
|
||||
class NetworkGroupValidator(BasicValidator):
|
||||
|
||||
single_schema = ng_scheme.single_scheme
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
d = cls.validate_json(data)
|
||||
node_group = objects.NodeGroup.get_by_uid(d.get('group_id'))
|
||||
|
||||
if not node_group:
|
||||
raise errors.InvalidData(
|
||||
"Node group with ID {0} does not exist".format(
|
||||
d.get('group_id'))
|
||||
)
|
||||
|
||||
if objects.NetworkGroup.get_from_node_group_by_name(
|
||||
node_group.id, d.get('name')):
|
||||
raise errors.AlreadyExists(
|
||||
"Network with name {0} already exists "
|
||||
"in node group {1}".format(d['name'], node_group.name)
|
||||
)
|
||||
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def validate_update(cls, data, **kwargs):
|
||||
return cls.validate(data)
|
||||
|
||||
@classmethod
|
||||
def validate_delete(cls, data, instance, force=False):
|
||||
if not instance.group_id:
|
||||
# Only default Admin-pxe network doesn't have group_id.
|
||||
# It cannot be deleted.
|
||||
raise errors.InvalidData(
|
||||
"Default Admin-pxe network cannot be deleted")
|
||||
|
@ -99,9 +99,11 @@ def upgrade():
|
||||
set_deployable_false_for_old_releases()
|
||||
upgrade_node_labels()
|
||||
extend_segmentation_type()
|
||||
network_groups_name_upgrade()
|
||||
|
||||
|
||||
def downgrade():
|
||||
network_groups_name_downgrade()
|
||||
downgrade_node_labels()
|
||||
extensions_field_downgrade()
|
||||
downgrade_cluster_ui_settings()
|
||||
@ -131,6 +133,29 @@ def downgrade():
|
||||
type_='foreignkey')
|
||||
|
||||
|
||||
def network_groups_name_upgrade():
|
||||
op.alter_column('network_groups',
|
||||
'name',
|
||||
type_=sa.String(length=50),
|
||||
existing_type=sa.Enum(
|
||||
'fuelweb_admin', 'storage',
|
||||
'management', 'public',
|
||||
'fixed', 'private',
|
||||
name='network_group_name'))
|
||||
drop_enum('network_group_name')
|
||||
|
||||
|
||||
def network_groups_name_downgrade():
|
||||
network_group_name = sa.Enum('fuelweb_admin', 'storage',
|
||||
'management', 'public',
|
||||
'fixed', 'private',
|
||||
name='network_group_name')
|
||||
network_group_name.create(op.get_bind(), checkfirst=False)
|
||||
op.execute('ALTER TABLE network_groups ALTER COLUMN name '
|
||||
'TYPE network_group_name '
|
||||
'USING name::text::network_group_name')
|
||||
|
||||
|
||||
def extend_node_model_upgrade():
|
||||
op.add_column(
|
||||
'node_nic_interfaces',
|
||||
|
@ -15,13 +15,11 @@
|
||||
# under the License.
|
||||
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import Enum
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import String
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun.db.sqlalchemy.models.base import Base
|
||||
from nailgun.db.sqlalchemy.models.fields import JSON
|
||||
|
||||
@ -51,8 +49,7 @@ class NetworkGroup(Base):
|
||||
__tablename__ = 'network_groups'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(Enum(*consts.NETWORKS, name='network_group_name'),
|
||||
nullable=False)
|
||||
name = Column(String(50), nullable=False)
|
||||
# can be nullable only for fuelweb admin net
|
||||
release = Column(Integer, ForeignKey('releases.id'))
|
||||
# can be nullable only for fuelweb admin net
|
||||
|
@ -50,3 +50,6 @@ from nailgun.objects.node_group import NodeGroupCollection
|
||||
|
||||
from nailgun.objects.plugin import Plugin
|
||||
from nailgun.objects.plugin import PluginCollection
|
||||
|
||||
from nailgun.objects.network_group import NetworkGroup
|
||||
from nailgun.objects.network_group import NetworkGroupCollection
|
||||
|
40
nailgun/nailgun/objects/network_group.py
Normal file
40
nailgun/nailgun/objects/network_group.py
Normal file
@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 nailgun.objects.serializers.network_group import NetworkGroupSerializer
|
||||
|
||||
from nailgun.db import db
|
||||
from nailgun.db.sqlalchemy.models import NetworkGroup as DBNetworkGroup
|
||||
from nailgun.objects import NailgunCollection
|
||||
from nailgun.objects import NailgunObject
|
||||
|
||||
|
||||
class NetworkGroup(NailgunObject):
|
||||
|
||||
model = DBNetworkGroup
|
||||
serializer = NetworkGroupSerializer
|
||||
|
||||
@classmethod
|
||||
def get_from_node_group_by_name(cls, node_group_id, network_name):
|
||||
ng = db().query(DBNetworkGroup).filter_by(group_id=node_group_id,
|
||||
name=network_name)
|
||||
return ng.first() if ng else None
|
||||
|
||||
|
||||
class NetworkGroupCollection(NailgunCollection):
|
||||
|
||||
single = NetworkGroup
|
31
nailgun/nailgun/objects/serializers/network_group.py
Normal file
31
nailgun/nailgun/objects/serializers/network_group.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 nailgun.objects.serializers.base import BasicSerializer
|
||||
|
||||
|
||||
class NetworkGroupSerializer(BasicSerializer):
|
||||
|
||||
fields = (
|
||||
'id',
|
||||
'name',
|
||||
'release',
|
||||
'vlan_start',
|
||||
'cidr',
|
||||
'gateway',
|
||||
'meta',
|
||||
'group_id'
|
||||
)
|
@ -797,3 +797,22 @@ class TestTunSegmentType(base.BaseAlembicMigrationTest):
|
||||
[self.meta.tables['neutron_config'].c.segmentation_type])).\
|
||||
fetchall()
|
||||
self.assertIn(('tun',), types)
|
||||
|
||||
|
||||
class TestStringNetworkGroupName(base.BaseAlembicMigrationTest):
|
||||
|
||||
def test_tun_segment_type_added(self):
|
||||
db.execute(
|
||||
self.meta.tables['network_groups'].insert(),
|
||||
[{
|
||||
'id': 3,
|
||||
'name': 'custom_name',
|
||||
'vlan_start': None,
|
||||
'cidr': '10.20.0.0/24',
|
||||
'gateway': '10.20.0.200',
|
||||
}])
|
||||
names = db.execute(
|
||||
sa.select(
|
||||
[self.meta.tables['network_groups'].c.name])). \
|
||||
fetchall()
|
||||
self.assertIn(('custom_name',), names)
|
||||
|
176
nailgun/nailgun/test/unit/test_network_group_handler.py
Normal file
176
nailgun/nailgun/test/unit/test_network_group_handler.py
Normal file
@ -0,0 +1,176 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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.serialization import jsonutils
|
||||
|
||||
from nailgun import objects
|
||||
from nailgun.test.base import BaseIntegrationTest
|
||||
from nailgun.utils import reverse
|
||||
|
||||
|
||||
class TestHandlers(BaseIntegrationTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHandlers, self).setUp()
|
||||
self.cluster = self.env.create_cluster(api=False)
|
||||
|
||||
def _create_network_group(self, expect_errors=False, **kwargs):
|
||||
ng = {
|
||||
"release": self.cluster.release.id,
|
||||
"name": "external",
|
||||
"vlan_start": 50,
|
||||
"cidr": "10.3.0.0/24",
|
||||
"gateway": "10.3.0.3",
|
||||
"group_id": objects.Cluster.get_default_group(self.cluster).id,
|
||||
"meta": {}
|
||||
}
|
||||
|
||||
ng.update(kwargs)
|
||||
|
||||
resp = self.app.post(
|
||||
reverse('NetworkGroupCollectionHandler'),
|
||||
jsonutils.dumps(ng),
|
||||
headers=self.default_headers,
|
||||
expect_errors=expect_errors,
|
||||
)
|
||||
|
||||
return resp
|
||||
|
||||
def test_create_network_group(self):
|
||||
resp = self._create_network_group(name='test')
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
def test_get_network_group(self):
|
||||
resp = self._create_network_group(name='test')
|
||||
self.assertEqual(201, resp.status_code)
|
||||
new_ng = jsonutils.loads(resp.body)
|
||||
|
||||
net_group = jsonutils.loads(resp.body)
|
||||
|
||||
resp = self.app.get(
|
||||
reverse(
|
||||
'NetworkGroupHandler',
|
||||
kwargs={'obj_id': net_group['id']}
|
||||
),
|
||||
headers=self.default_headers
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(jsonutils.loads(resp.body), new_ng)
|
||||
|
||||
def test_delete_network_group(self):
|
||||
resp = self._create_network_group(name='test')
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
net_group = jsonutils.loads(resp.body)
|
||||
|
||||
resp = self.app.delete(
|
||||
reverse(
|
||||
'NetworkGroupHandler',
|
||||
kwargs={'obj_id': net_group['id']}
|
||||
),
|
||||
headers=self.default_headers
|
||||
)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
def test_cannot_delete_admin_network_group(self):
|
||||
admin = objects.Cluster.get_network_manager().get_admin_network_group()
|
||||
resp = self.app.delete(
|
||||
reverse(
|
||||
'NetworkGroupHandler',
|
||||
kwargs={'obj_id': admin.id}
|
||||
),
|
||||
headers=self.default_headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(400, resp.status_code)
|
||||
self.assertRegexpMatches(resp.json_body["message"],
|
||||
'Default Admin-pxe network cannot be deleted')
|
||||
|
||||
def test_create_network_group_non_default_name(self):
|
||||
resp = self._create_network_group(name='test')
|
||||
new_ng = jsonutils.loads(resp.body)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.assertEqual('test', new_ng['name'])
|
||||
|
||||
def test_modify_network_group(self):
|
||||
resp = self._create_network_group(name='test')
|
||||
new_ng = jsonutils.loads(resp.body)
|
||||
|
||||
new_ng['name'] = 'test2'
|
||||
|
||||
resp = self.app.put(
|
||||
reverse(
|
||||
'NetworkGroupHandler',
|
||||
kwargs={'obj_id': new_ng['id']}
|
||||
),
|
||||
jsonutils.dumps(new_ng),
|
||||
headers=self.default_headers
|
||||
)
|
||||
updated_ng = jsonutils.loads(resp.body)
|
||||
|
||||
self.assertEquals('test2', updated_ng['name'])
|
||||
|
||||
def test_duplicate_network_name_on_creation(self):
|
||||
resp = self._create_network_group()
|
||||
self.assertEqual(201, resp.status_code)
|
||||
resp = self._create_network_group(expect_errors=True)
|
||||
self.assertEqual(409, resp.status_code)
|
||||
self.assertRegexpMatches(resp.json_body["message"],
|
||||
'Network with name .* already exists')
|
||||
|
||||
def test_duplicate_network_name_on_change(self):
|
||||
resp = self._create_network_group(name='test')
|
||||
new_ng = jsonutils.loads(resp.body)
|
||||
|
||||
new_ng['name'] = 'public'
|
||||
|
||||
resp = self.app.put(
|
||||
reverse(
|
||||
'NetworkGroupHandler',
|
||||
kwargs={'obj_id': new_ng['id']}
|
||||
),
|
||||
jsonutils.dumps(new_ng),
|
||||
headers=self.default_headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(409, resp.status_code)
|
||||
self.assertRegexpMatches(resp.json_body["message"],
|
||||
'Network with name .* already exists')
|
||||
|
||||
def test_invalid_group_id_on_creation(self):
|
||||
resp = self._create_network_group(expect_errors=True, group_id=-1)
|
||||
self.assertEqual(400, resp.status_code)
|
||||
self.assertRegexpMatches(resp.json_body["message"],
|
||||
'Node group with ID -1 does not exist')
|
||||
|
||||
def test_invalid_group_id_on_change(self):
|
||||
resp = self._create_network_group(name='test')
|
||||
new_ng = jsonutils.loads(resp.body)
|
||||
|
||||
new_ng['group_id'] = -1
|
||||
|
||||
resp = self.app.put(
|
||||
reverse(
|
||||
'NetworkGroupHandler',
|
||||
kwargs={'obj_id': new_ng['id']}
|
||||
),
|
||||
jsonutils.dumps(new_ng),
|
||||
headers=self.default_headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(400, resp.status_code)
|
||||
self.assertRegexpMatches(resp.json_body["message"],
|
||||
'Node group with ID -1 does not exist')
|
Loading…
Reference in New Issue
Block a user