Add Network object
This adds a Network object to provide services and modeling for nova-network and its use of the current SQLAlchemy Network model. Related to blueprint nova-network-objects Change-Id: I7f943cda32fc98b3b03cdbb5d68e0c741053afaa
This commit is contained in:
parent
f6c341b4b2
commit
e134f81d85
|
@ -24,3 +24,4 @@ def register_all():
|
|||
__import__('nova.objects.migration')
|
||||
__import__('nova.objects.quotas')
|
||||
__import__('nova.objects.virtual_interface')
|
||||
__import__('nova.objects.network')
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
# Copyright 2014 Red Hat, 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.
|
||||
|
||||
import netaddr
|
||||
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.objects import base as obj_base
|
||||
from nova.objects import fields
|
||||
|
||||
|
||||
class Network(obj_base.NovaPersistentObject, obj_base.NovaObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
'label': fields.StringField(),
|
||||
'injected': fields.BooleanField(),
|
||||
'cidr': fields.IPV4NetworkField(nullable=True),
|
||||
'cidr_v6': fields.IPV6NetworkField(nullable=True),
|
||||
'multi_host': fields.BooleanField(),
|
||||
'netmask': fields.IPV4AddressField(nullable=True),
|
||||
'gateway': fields.IPV4AddressField(nullable=True),
|
||||
'broadcast': fields.IPV4AddressField(nullable=True),
|
||||
'netmask_v6': fields.IPV6AddressField(nullable=True),
|
||||
'gateway_v6': fields.IPV6AddressField(nullable=True),
|
||||
'bridge': fields.StringField(nullable=True),
|
||||
'bridge_interface': fields.StringField(nullable=True),
|
||||
'dns1': fields.IPAddressField(nullable=True),
|
||||
'dns2': fields.IPAddressField(nullable=True),
|
||||
'vlan': fields.IntegerField(nullable=True),
|
||||
'vpn_public_address': fields.IPAddressField(nullable=True),
|
||||
'vpn_public_port': fields.IntegerField(nullable=True),
|
||||
'vpn_private_address': fields.IPAddressField(nullable=True),
|
||||
'dhcp_start': fields.IPV4AddressField(nullable=True),
|
||||
'rxtx_base': fields.IntegerField(nullable=True),
|
||||
'project_id': fields.UUIDField(nullable=True),
|
||||
'priority': fields.IntegerField(nullable=True),
|
||||
'host': fields.StringField(nullable=True),
|
||||
'uuid': fields.UUIDField(),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _convert_legacy_ipv6_netmask(netmask):
|
||||
"""Handle netmask_v6 possibilities from the database.
|
||||
|
||||
Historically, this was stored as just an integral CIDR prefix,
|
||||
but in the future it should be stored as an actual netmask.
|
||||
Be tolerant of either here.
|
||||
"""
|
||||
try:
|
||||
prefix = int(netmask)
|
||||
return netaddr.IPNetwork('1::/%i' % prefix).netmask
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return netaddr.IPNetwork(netmask).netmask
|
||||
except netaddr.AddrFormatError:
|
||||
raise ValueError('IPv6 netmask "%s" must be a netmask '
|
||||
'or integral prefix' % netmask)
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(context, network, db_network):
|
||||
for field in network.fields:
|
||||
db_value = db_network[field]
|
||||
if field is 'netmask_v6' and db_value is not None:
|
||||
db_value = network._convert_legacy_ipv6_netmask(db_value)
|
||||
network[field] = db_value
|
||||
network._context = context
|
||||
network.obj_reset_changes()
|
||||
return network
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def get_by_id(cls, context, network_id, project_only='allow_none'):
|
||||
db_network = db.network_get(context, network_id,
|
||||
project_only=project_only)
|
||||
return cls._from_db_object(context, cls(), db_network)
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, network_uuid):
|
||||
db_network = db.network_get_by_uuid(context, network_uuid)
|
||||
return cls._from_db_object(context, cls(), db_network)
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def get_by_cidr(cls, context, cidr):
|
||||
db_network = db.network_get_by_cidr(context, cidr)
|
||||
return cls._from_db_object(context, cls(), db_network)
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def associate(cls, context, project_id, network_id=None, force=False):
|
||||
db.network_associate(context, project_id, network_id=network_id,
|
||||
force=force)
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def disassociate(cls, context, network_id, host=False, project=False):
|
||||
db.network_disassociate(context, network_id, host, project)
|
||||
|
||||
def _get_primitive_changes(self):
|
||||
changes = {}
|
||||
for key, value in self.obj_get_changes().items():
|
||||
if isinstance(value, netaddr.IPAddress):
|
||||
changes[key] = str(value)
|
||||
else:
|
||||
changes[key] = value
|
||||
return changes
|
||||
|
||||
@obj_base.remotable
|
||||
def create(self, context):
|
||||
updates = self._get_primitive_changes()
|
||||
if 'id' in updates:
|
||||
raise exception.ObjectActionError(action='create',
|
||||
reason='already created')
|
||||
db_network = db.network_create_safe(context, updates)
|
||||
self._from_db_object(context, self, db_network)
|
||||
|
||||
@obj_base.remotable
|
||||
def destroy(self, context):
|
||||
db.network_delete_safe(context, self.id)
|
||||
self.deleted = True
|
||||
self.obj_reset_changes(['deleted'])
|
||||
|
||||
@obj_base.remotable
|
||||
def save(self, context):
|
||||
updates = self._get_primitive_changes()
|
||||
if 'netmask_v6' in updates:
|
||||
# NOTE(danms): For some reason, historical code stores the
|
||||
# IPv6 netmask as just the CIDR mask length, so convert that
|
||||
# back here before saving for now.
|
||||
updates['netmask_v6'] = netaddr.IPNetwork(
|
||||
updates['netmask_v6']).netmask
|
||||
set_host = 'host' in updates
|
||||
if set_host:
|
||||
db.network_set_host(context, self.id, updates.pop('host'))
|
||||
if updates:
|
||||
db_network = db.network_update(context, self.id, updates)
|
||||
elif set_host:
|
||||
db_network = db.network_get(context, self.id)
|
||||
else:
|
||||
db_network = None
|
||||
if db_network is not None:
|
||||
self._from_db_object(context, self, db_network)
|
||||
|
||||
|
||||
class NetworkList(obj_base.ObjectListBase, obj_base.NovaObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('Network'),
|
||||
}
|
||||
child_versions = {
|
||||
'1.0': '1.0',
|
||||
}
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def get_all(cls, context, project_only='allow_none'):
|
||||
db_networks = db.network_get_all(context, project_only)
|
||||
return obj_base.obj_make_list(context, cls(), Network, db_networks)
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def get_by_uuids(cls, context, network_uuids, project_only='allow_none'):
|
||||
db_networks = db.network_get_all_by_uuids(context, network_uuids,
|
||||
project_only)
|
||||
return obj_base.obj_make_list(context, cls(), Network, db_networks)
|
||||
|
||||
@obj_base.remotable_classmethod
|
||||
def get_by_host(cls, context, host):
|
||||
db_networks = db.network_get_all_by_host(context, host)
|
||||
return obj_base.obj_make_list(context, cls(), Network, db_networks)
|
|
@ -0,0 +1,203 @@
|
|||
# Copyright 2014 Red Hat, 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.
|
||||
|
||||
import mock
|
||||
import netaddr
|
||||
|
||||
from nova.objects import network as network_obj
|
||||
from nova.tests.objects import test_objects
|
||||
|
||||
|
||||
fake_network = {
|
||||
'deleted': False,
|
||||
'created_at': None,
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'id': 1,
|
||||
'label': 'Fake Network',
|
||||
'injected': False,
|
||||
'cidr': '192.168.1.0/24',
|
||||
'cidr_v6': '1234::/64',
|
||||
'multi_host': False,
|
||||
'netmask': '255.255.255.0',
|
||||
'gateway': '192.168.1.1',
|
||||
'broadcast': '192.168.1.255',
|
||||
'netmask_v6': 64,
|
||||
'gateway_v6': '1234::1',
|
||||
'bridge': 'br100',
|
||||
'bridge_interface': 'eth0',
|
||||
'dns1': '8.8.8.8',
|
||||
'dns2': '8.8.4.4',
|
||||
'vlan': None,
|
||||
'vpn_public_address': None,
|
||||
'vpn_public_port': None,
|
||||
'vpn_private_address': None,
|
||||
'dhcp_start': '192.168.1.10',
|
||||
'rxtx_base': None,
|
||||
'project_id': None,
|
||||
'priority': None,
|
||||
'host': None,
|
||||
'uuid': 'fake-uuid',
|
||||
}
|
||||
|
||||
|
||||
class _TestNetworkObject(object):
|
||||
def _compare(self, obj, db_obj):
|
||||
for field in obj.fields:
|
||||
db_val = db_obj[field]
|
||||
obj_val = obj[field]
|
||||
if isinstance(obj_val, netaddr.IPAddress):
|
||||
obj_val = str(obj_val)
|
||||
if isinstance(obj_val, netaddr.IPNetwork):
|
||||
obj_val = str(obj_val)
|
||||
if field == 'netmask_v6':
|
||||
db_val = str(netaddr.IPNetwork('1::/%i' % db_val).netmask)
|
||||
self.assertEqual(db_val, obj_val)
|
||||
|
||||
@mock.patch('nova.db.network_get')
|
||||
def test_get_by_id(self, get):
|
||||
get.return_value = fake_network
|
||||
network = network_obj.Network.get_by_id(self.context, 'foo')
|
||||
self._compare(network, fake_network)
|
||||
get.assert_called_once_with(self.context, 'foo',
|
||||
project_only='allow_none')
|
||||
|
||||
@mock.patch('nova.db.network_get_by_uuid')
|
||||
def test_get_by_uuid(self, get):
|
||||
get.return_value = fake_network
|
||||
network = network_obj.Network.get_by_uuid(self.context, 'foo')
|
||||
self._compare(network, fake_network)
|
||||
get.assert_called_once_with(self.context, 'foo')
|
||||
|
||||
@mock.patch('nova.db.network_get_by_cidr')
|
||||
def test_get_by_cidr(self, get):
|
||||
get.return_value = fake_network
|
||||
network = network_obj.Network.get_by_cidr(self.context,
|
||||
'192.168.1.0/24')
|
||||
self._compare(network, fake_network)
|
||||
get.assert_called_once_with(self.context, '192.168.1.0/24')
|
||||
|
||||
@mock.patch('nova.db.network_update')
|
||||
@mock.patch('nova.db.network_set_host')
|
||||
def test_save(self, set_host, update):
|
||||
result = dict(fake_network, injected=True)
|
||||
network = network_obj.Network._from_db_object(self.context,
|
||||
network_obj.Network(),
|
||||
fake_network)
|
||||
network.obj_reset_changes()
|
||||
network.save()
|
||||
network.label = 'bar'
|
||||
update.return_value = result
|
||||
network.save()
|
||||
update.assert_called_once_with(self.context, network.id,
|
||||
{'label': 'bar'})
|
||||
self.assertFalse(set_host.called)
|
||||
self._compare(network, result)
|
||||
|
||||
@mock.patch('nova.db.network_update')
|
||||
@mock.patch('nova.db.network_set_host')
|
||||
@mock.patch('nova.db.network_get')
|
||||
def test_save_with_host(self, get, set_host, update):
|
||||
result = dict(fake_network, injected=True)
|
||||
network = network_obj.Network._from_db_object(self.context,
|
||||
network_obj.Network(),
|
||||
fake_network)
|
||||
network.obj_reset_changes()
|
||||
network.host = 'foo'
|
||||
get.return_value = result
|
||||
network.save()
|
||||
set_host.assert_called_once_with(self.context, network.id, 'foo')
|
||||
self.assertFalse(update.called)
|
||||
self._compare(network, result)
|
||||
|
||||
@mock.patch('nova.db.network_update')
|
||||
@mock.patch('nova.db.network_set_host')
|
||||
def test_save_with_host_and_other(self, set_host, update):
|
||||
result = dict(fake_network, injected=True)
|
||||
network = network_obj.Network._from_db_object(self.context,
|
||||
network_obj.Network(),
|
||||
fake_network)
|
||||
network.obj_reset_changes()
|
||||
network.host = 'foo'
|
||||
network.label = 'bar'
|
||||
update.return_value = result
|
||||
network.save()
|
||||
set_host.assert_called_once_with(self.context, network.id, 'foo')
|
||||
update.assert_called_once_with(self.context, network.id,
|
||||
{'label': 'bar'})
|
||||
self._compare(network, result)
|
||||
|
||||
@mock.patch('nova.db.network_associate')
|
||||
def test_associate(self, associate):
|
||||
network_obj.Network.associate(self.context, 'project',
|
||||
network_id=123)
|
||||
associate.assert_called_once_with(self.context, 'project',
|
||||
network_id=123, force=False)
|
||||
|
||||
@mock.patch('nova.db.network_disassociate')
|
||||
def test_disassociate(self, disassociate):
|
||||
network_obj.Network.disassociate(self.context, 123,
|
||||
host=True, project=True)
|
||||
disassociate.assert_called_once_with(self.context, 123, True, True)
|
||||
|
||||
@mock.patch('nova.db.network_create_safe')
|
||||
def test_create(self, create):
|
||||
create.return_value = fake_network
|
||||
network = network_obj.Network(context=self.context, label='foo')
|
||||
network.create()
|
||||
create.assert_called_once_with(self.context, {'label': 'foo'})
|
||||
self._compare(network, fake_network)
|
||||
|
||||
@mock.patch('nova.db.network_delete_safe')
|
||||
def test_destroy(self, delete):
|
||||
network = network_obj.Network(context=self.context, id=123)
|
||||
network.destroy()
|
||||
delete.assert_called_once_with(self.context, 123)
|
||||
self.assertTrue(network.deleted)
|
||||
self.assertNotIn('deleted', network.obj_what_changed())
|
||||
|
||||
@mock.patch('nova.db.network_get_all')
|
||||
def test_get_all(self, get_all):
|
||||
get_all.return_value = [fake_network]
|
||||
networks = network_obj.NetworkList.get_all(self.context)
|
||||
self.assertEqual(1, len(networks))
|
||||
get_all.assert_called_once_with(self.context, 'allow_none')
|
||||
self._compare(networks[0], fake_network)
|
||||
|
||||
@mock.patch('nova.db.network_get_all_by_uuids')
|
||||
def test_get_all_by_uuids(self, get_all):
|
||||
get_all.return_value = [fake_network]
|
||||
networks = network_obj.NetworkList.get_by_uuids(self.context,
|
||||
['foo'])
|
||||
self.assertEqual(1, len(networks))
|
||||
get_all.assert_called_once_with(self.context, ['foo'], 'allow_none')
|
||||
self._compare(networks[0], fake_network)
|
||||
|
||||
@mock.patch('nova.db.network_get_all_by_host')
|
||||
def test_get_all_by_host(self, get_all):
|
||||
get_all.return_value = [fake_network]
|
||||
networks = network_obj.NetworkList.get_by_host(self.context, 'host')
|
||||
self.assertEqual(1, len(networks))
|
||||
get_all.assert_called_once_with(self.context, 'host')
|
||||
self._compare(networks[0], fake_network)
|
||||
|
||||
|
||||
class TestNetworkObject(test_objects._LocalTest,
|
||||
_TestNetworkObject):
|
||||
pass
|
||||
|
||||
|
||||
class TestRemoteNetworkObject(test_objects._RemoteTest,
|
||||
_TestNetworkObject):
|
||||
pass
|
Loading…
Reference in New Issue