Commit 49929c5e authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

Add support for IPv6

- Extend the Network model with fields for holding the IPv6 subnet and
  gateway.
- Update network API methods.
- Modify ganeti hooks to return IPv6 address for all network interfaces.
  IPv6 is now derived from the IPv6 subnet of the network in Ganeti,
  instead of the old setting (PUBLIC_IPV6_PREFIX)
- Remove obsolete setting (`PUBLIC_IPV6_PREFIX`)
- Update management commands for IPv6
parent 21c8e5de
......@@ -59,6 +59,11 @@ class Command(BaseCommand):
dest='public',
default=False,
help="List only public networks"),
make_option('--ipv6',
action='store_true',
dest='ipv6',
default=False,
help="Show IPv6 information of the network"),
)
def handle(self, *args, **options):
......@@ -74,9 +79,16 @@ class Command(BaseCommand):
if options['public']:
networks = networks.filter(public=True)
labels = ('id', 'name', 'owner', 'subnet', 'gateway', 'mac_prefix',
'dhcp', 'state', 'link', 'vms', 'public')
columns = (3, 12, 30, 14, 14, 14, 8, 10, 12, 4, 6)
labels = ['id', 'name', 'type', 'owner',
'mac_prefix', 'dhcp', 'state', 'link', 'vms', 'public']
columns = [3, 16, 22, 30, 10, 6, 8, 12, 4, 6]
if options['ipv6']:
labels.extend(['IPv6 Subnet', 'IPv6 Gateway'])
columns.extend([16, 16])
else:
labels.extend(['IPv4 Subnet', 'IPv4 Gateway'])
columns.extend([14, 14])
if not options['csv']:
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
......@@ -85,17 +97,20 @@ class Command(BaseCommand):
self.stdout.write(sep + '\n')
for network in networks:
fields = (str(network.id),
fields = [str(network.id),
network.name,
network.userid or '',
network.subnet,
network.gateway or '',
network.mac_prefix or '',
str(network.dhcp),
network.state,
network.link or '',
str(network.machines.count()),
format_bool(network.public))
format_bool(network.public)]
if options['ipv6']:
fields.extend([network.subnet6 or '', network.gateway6 or ''])
else:
fields.extend([network.subnet, network.gateway or ''])
if options['csv']:
line = '|'.join(fields)
......
......@@ -57,6 +57,12 @@ class Command(BaseCommand):
make_option('--gateway',
dest='gateway',
help="Set network's gateway"),
make_option('--subnet6',
dest='subnet6',
help="Set network's IPv6 subnet"),
make_option('--gateway6',
dest='gateway6',
help="Set network's IPv6 gateway"),
make_option('--dhcp',
dest='dhcp',
help="Set if network will use nfdhcp"),
......@@ -92,6 +98,14 @@ class Command(BaseCommand):
if gateway is not None:
network.gateway = gateway
subnet6 = options.get('subnet6')
if subnet6 is not None:
network.subnet6 = subnet6
gateway6 = options.get('gateway6')
if gateway6 is not None:
network.gateway6 = gateway6
dhcp = options.get('dhcp')
if dhcp is not None:
network.dhcp = dhcp
......
......@@ -85,7 +85,9 @@ def network_to_dict(network, user_id, detail=True):
d = {'id': network_id, 'name': network.name}
if detail:
d['cidr'] = network.subnet
d['cidr6'] = network.subnet6
d['gateway'] = network.gateway
d['gateway6'] = network.gateway6
d['dhcp'] = network.dhcp
d['type'] = network.type
d['updated'] = util.isoformat(network.updated)
......@@ -156,7 +158,9 @@ def create_network(request):
name = d['name']
# TODO: Fix this temp values:
subnet = d.get('cidr', '192.168.1.0/24')
subnet6 = d.get('cidr6', None)
gateway = d.get('gateway', None)
gateway6 = d.get('gateway6', None)
type = d.get('type', 'PRIVATE_MAC_FILTERED')
dhcp = d.get('dhcp', True)
except (KeyError, ValueError):
......@@ -185,7 +189,9 @@ def create_network(request):
name=name,
userid=request.user_uniq,
subnet=subnet,
subnet6=subnet6,
gateway=gateway,
gateway6=gateway6,
dhcp=dhcp,
type=type,
link=link,
......
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Network.subnet6'
db.add_column('db_network', 'subnet6', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
# Adding field 'Network.gateway6'
db.add_column('db_network', 'gateway6', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'Network.subnet6'
db.delete_column('db_network', 'subnet6')
# Deleting field 'Network.gateway6'
db.delete_column('db_network', 'gateway6')
models = {
'db.backend': {
'Meta': {'object_name': 'Backend'},
'clustername': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
'ctotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'drained': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'dtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mfree': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'mtotal': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'offline': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'password_hash': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'pinst_cnt': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'port': ('django.db.models.fields.PositiveIntegerField', [], {'default': '5080'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'})
},
'db.backendnetwork': {
'Meta': {'object_name': 'BackendNetwork'},
'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'networks'", 'to': "orm['db.Backend']"}),
'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'backend_networks'", 'to': "orm['db.Network']"}),
'operstate': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '30'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'db.bridgepool': {
'Meta': {'object_name': 'BridgePool'},
'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
'value': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
},
'db.flavor': {
'Meta': {'unique_together': "(('cpu', 'ram', 'disk', 'disk_template'),)", 'object_name': 'Flavor'},
'cpu': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'disk_template': ('django.db.models.fields.CharField', [], {'default': "'drbd'", 'max_length': '32'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'db.macprefixpool': {
'Meta': {'object_name': 'MacPrefixPool'},
'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
'value': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
},
'db.network': {
'Meta': {'object_name': 'Network'},
'action': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '32', 'null': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'dhcp': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'gateway': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'gateway6': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'link': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'mac_prefix': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'through': "orm['db.NetworkInterface']", 'symmetrical': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '32'}),
'subnet': ('django.db.models.fields.CharField', [], {'default': "'10.0.0.0/24'", 'max_length': '32'}),
'subnet6': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'default': "'PRIVATE_PHYSICAL_VLAN'", 'max_length': '50'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'userid': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'})
},
'db.networkinterface': {
'Meta': {'object_name': 'NetworkInterface'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'dirty': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'firewall_profile': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'index': ('django.db.models.fields.IntegerField', [], {}),
'ipv4': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}),
'ipv6': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
'mac': ('django.db.models.fields.CharField', [], {'max_length': '17', 'null': 'True'}),
'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.VirtualMachine']"}),
'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.Network']"}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'db.virtualmachine': {
'Meta': {'object_name': 'VirtualMachine'},
'action': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backend': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'virtual_machines'", 'null': 'True', 'to': "orm['db.Backend']"}),
'backend_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'backendtime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1, 1, 1, 0, 0)'}),
'buildpercentage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}),
'hostid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'imageid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'operstate': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'userid': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'db.virtualmachinemetadata': {
'Meta': {'unique_together': "(('meta_key', 'vm'),)", 'object_name': 'VirtualMachineMetadata'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
'vm': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'metadata'", 'to': "orm['db.VirtualMachine']"})
}
}
complete_apps = ['db']
......@@ -440,7 +440,9 @@ class Network(models.Model):
name = models.CharField('Network Name', max_length=128)
userid = models.CharField('User ID of the owner', max_length=128, null=True)
subnet = models.CharField('Subnet', max_length=32, default='10.0.0.0/24')
subnet6 = models.CharField('IPv6 Subnet', max_length=64, null=True)
gateway = models.CharField('Gateway', max_length=32, null=True)
gateway6 = models.CharField('IPv6 Gateway', max_length=64, null=True)
dhcp = models.BooleanField('DHCP', default=True)
type = models.CharField(choices=NETWORK_TYPES, max_length=50,
default='PRIVATE_PHYSICAL_VLAN')
......
......@@ -40,10 +40,9 @@ import subprocess
from optparse import make_option
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from synnefo.db.models import VirtualMachine
from synnefo.db.models import VirtualMachine, Network
from synnefo.logic import reconciliation, backend, utils
......@@ -239,8 +238,10 @@ class Command(BaseCommand):
vm.nics.all.delete()
continue
for index, nic in nics.items():
net_id = utils.id_from_network_name(nic['network'])
subnet6 = Network.objects.get(id=net_id).subnet6
# Produce ipv6
ipv6 = mac2eui64(nic['mac'], settings.PUBLIC_IPV6_PREFIX)
ipv6 = subnet6 and mac2eui64(nic['mac'], subnet6) or None
nic['ipv6'] = ipv6
# Rename ipv4 to ip
nic['ip'] = nic['ipv4']
......
......@@ -76,27 +76,33 @@ def ganeti_net_status(logger, environ):
"""
nics = {}
key_to_attr = { 'IP': 'ip', 'MAC': 'mac', 'BRIDGE': 'link', 'NETWORK' : 'network' }
key_to_attr = {'IP': 'ip',
'MAC': 'mac',
'BRIDGE': 'link',
'NETWORK': 'network'}
for env in environ.keys():
if env.startswith("GANETI_INSTANCE_NIC"):
s = env.replace("GANETI_INSTANCE_NIC", "").split('_', 1)
if len(s) == 2 and s[0].isdigit() and s[1] in ('MAC', 'IP', 'BRIDGE', 'NETWORK'):
if len(s) == 2 and s[0].isdigit() and\
s[1] in ('MAC', 'IP', 'BRIDGE', 'NETWORK'):
index = int(s[0])
key = key_to_attr[s[1]]
if nics.has_key(index):
if index in nics:
nics[index][key] = environ[env]
else:
nics[index] = { key: environ[env] }
nics[index] = {key: environ[env]}
# IPv6 support:
#
# The IPv6 of NIC with index 0 [the public NIC]
# is derived using an EUI64 scheme.
if index == 0 and key == 'mac':
nics[0]['ipv6'] = mac2eui64(nics[0]['mac'],
settings.PUBLIC_IPV6_PREFIX)
# The IPv6 is derived using an EUI64 scheme.
if key == 'mac':
subnet6 = environ.get("GANETI_INSTANCE_NIC" + s[0] +
"_NETWORK_SUBNET6", None)
if subnet6:
nics[index]['ipv6'] = mac2eui64(nics[index]['mac'],
subnet6)
# Amend notification with firewall settings
tags = environ.get('GANETI_INSTANCE_TAGS', '')
......@@ -158,7 +164,7 @@ class GanetiHook():
# FIXME: We need a reconciliation mechanism between the DB and
# Ganeti, for cases exactly like this.
self.client = AMQPClient(max_retries= 2*len(settings.AMQP_HOSTS))
self.client = AMQPClient(max_retries=2 * len(settings.AMQP_HOSTS))
self.client.connect()
def on_master(self):
......@@ -176,6 +182,7 @@ class GanetiHook():
body=msg)
self.client.close()
class PostStartHook(GanetiHook):
"""Post-instance-startup Ganeti Hook.
......
......@@ -2,7 +2,6 @@
BACKEND_PREFIX_ID = "snf-"
EXCHANGE_GANETI = "ganeti"
PUBLIC_IPV6_PREFIX = "2001:db8::/64"
# List of RabbitMQ endpoints
AMQP_HOSTS = ["amqp://username:password@host:port"]
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment