Add Neutron support

Currently create instance doesnt work in OS installation with Neutron.
To get it work, additional parameter 'nics' should be specified
in Nova 'create' call.
This change allows user to pass 'nics' parameter when create instance.
Also possible to specify list of network IDs which should be attached
to instance if networks are not specified by user.

Closes-Bug: #1257838

Change-Id: Ifd98a880dfa22e9677e802bcd779acd9146ec671
This commit is contained in:
Andrey Shestakov 2013-12-25 15:05:40 +02:00
parent dc0504de51
commit bd54d99aa0
11 changed files with 101 additions and 28 deletions

View File

@ -85,6 +85,12 @@ volume = {
}
}
nics = {
"type": "array",
"items": {
"type": "object",
}
}
databases_ref_list = {
"type": "array",
@ -199,7 +205,8 @@ instance = {
"type": non_empty_string,
"version": non_empty_string
}
}
},
"nics": nics
}
}
}

View File

@ -238,6 +238,11 @@ common_opts = [
default=['atom', 'json', 'xml'],
help='Filetype endings not to be reattached to an id '
'by the utils method correct_id_with_req.'),
cfg.ListOpt('default_neutron_networks',
default=[],
help='List of network IDs which should be attached'
' to instance when networks are not specified'
' in API call.'),
]
CONF = cfg.CONF

View File

@ -451,7 +451,7 @@ class Instance(BuiltInstance):
@classmethod
def create(cls, context, name, flavor_id, image_id, databases, users,
datastore, datastore_version, volume_size, backup_id,
availability_zone=None):
availability_zone=None, nics=None):
client = create_nova_client(context)
try:
@ -481,6 +481,11 @@ class Instance(BuiltInstance):
raise exception.BackupFileNotFound(
location=backup_info.location)
if not nics and CONF.default_neutron_networks:
nics = []
for net_id in CONF.default_neutron_networks:
nics.append({"net-id": net_id})
def _create_resources():
db_info = DBInstance.create(name=name, flavor_id=flavor_id,
@ -513,7 +518,7 @@ class Instance(BuiltInstance):
datastore_version.packages,
volume_size, backup_id,
availability_zone,
root_password)
root_password, nics)
return SimpleInstance(context, db_info, service_status,
root_password)

View File

@ -215,11 +215,16 @@ class InstanceController(wsgi.Controller):
else:
availability_zone = None
if 'nics' in body['instance']:
nics = body['instance']['nics']
else:
nics = None
instance = models.Instance.create(context, name, flavor_id,
image_id, databases, users,
datastore, datastore_version,
volume_size, backup_id,
availability_zone)
availability_zone, nics)
view = views.InstanceDetailView(instance, req=req)
return wsgi.Result(view.data(), 200)

View File

@ -102,7 +102,7 @@ class API(proxy.RpcProxy):
def create_instance(self, instance_id, name, flavor,
image_id, databases, users, datastore_manager,
packages, volume_size, backup_id=None,
availability_zone=None, root_password=None):
availability_zone=None, root_password=None, nics=None):
LOG.debug("Making async call to create instance %s " % instance_id)
self.cast(self.context,
self.make_msg("create_instance",
@ -117,4 +117,4 @@ class API(proxy.RpcProxy):
volume_size=volume_size,
backup_id=backup_id,
availability_zone=availability_zone,
root_password=root_password))
root_password=root_password, nics=nics))

View File

@ -83,12 +83,12 @@ class Manager(periodic_task.PeriodicTasks):
def create_instance(self, context, instance_id, name, flavor,
image_id, databases, users, datastore_manager,
packages, volume_size, backup_id, availability_zone,
root_password):
root_password, nics):
instance_tasks = FreshInstanceTasks.load(context, instance_id)
instance_tasks.create_instance(flavor, image_id, databases, users,
datastore_manager, packages,
volume_size, backup_id,
availability_zone, root_password)
availability_zone, root_password, nics)
if CONF.exists_notification_transformer:
@periodic_task.periodic_task(

View File

@ -147,7 +147,7 @@ class ConfigurationMixin(object):
class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def create_instance(self, flavor, image_id, databases, users,
datastore_manager, packages, volume_size,
backup_id, availability_zone, root_password):
backup_id, availability_zone, root_password, nics):
LOG.debug(_("begin create_instance for id: %s") % self.id)
security_groups = None
@ -175,7 +175,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
image_id,
datastore_manager,
volume_size,
availability_zone)
availability_zone,
nics)
elif use_nova_server_volume:
volume_info = self._create_server_volume(
flavor['id'],
@ -183,7 +184,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
security_groups,
datastore_manager,
volume_size,
availability_zone)
availability_zone,
nics)
else:
volume_info = self._create_server_volume_individually(
flavor['id'],
@ -191,7 +193,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
security_groups,
datastore_manager,
volume_size,
availability_zone)
availability_zone,
nics)
config = self._render_config(datastore_manager, flavor, self.id)
@ -302,7 +305,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server_volume(self, flavor_id, image_id, security_groups,
datastore_manager, volume_size,
availability_zone):
availability_zone, nics):
LOG.debug(_("begin _create_server_volume for id: %s") % self.id)
server = None
try:
@ -321,7 +324,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
name, image_id, flavor_id,
files=files, volume=volume_ref,
security_groups=security_groups,
availability_zone=availability_zone)
availability_zone=availability_zone, nics=nics)
LOG.debug(_("Created new compute instance %(server_id)s "
"for id: %(id)s") %
{'server_id': server.id, 'id': self.id})
@ -349,14 +352,16 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server_volume_heat(self, flavor, image_id,
datastore_manager,
volume_size, availability_zone):
volume_size, availability_zone, nics):
LOG.debug(_("begin _create_server_volume_heat for id: %s") % self.id)
try:
client = create_heat_client(self.context)
ifaces, ports = self._build_heat_nics(nics)
template_obj = template.load_heat_template(datastore_manager)
heat_template_unicode = template_obj.render(
volume_support=CONF.trove_volume_support)
volume_support=CONF.trove_volume_support,
ifaces=ifaces, ports=ports)
try:
heat_template = heat_template_unicode.encode('utf-8')
except UnicodeEncodeError:
@ -422,7 +427,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server_volume_individually(self, flavor_id, image_id,
security_groups, datastore_manager,
volume_size,
availability_zone):
availability_zone, nics):
LOG.debug(_("begin _create_server_volume_individually for id: %s") %
self.id)
server = None
@ -432,7 +437,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
server = self._create_server(flavor_id, image_id, security_groups,
datastore_manager,
block_device_mapping,
availability_zone)
availability_zone, nics)
server_id = server.id
# Save server ID.
self.update_db(compute_instance_id=server_id)
@ -522,7 +527,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server(self, flavor_id, image_id, security_groups,
datastore_manager, block_device_mapping,
availability_zone):
availability_zone, nics):
files = {"/etc/guest_info": ("[DEFAULT]\nguest_id=%s\n"
"datastore_manager=%s\n"
"tenant_id=%s\n" %
@ -542,7 +547,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
server = self.nova_client.servers.create(
name, image_id, flavor_id, files=files, userdata=userdata,
security_groups=security_groups, block_device_mapping=bdmap,
availability_zone=availability_zone)
availability_zone=availability_zone, nics=nics)
LOG.debug(_("Created new compute instance %(server_id)s "
"for id: %(id)s") %
{'server_id': server.id, 'id': self.id})
@ -616,6 +621,27 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
)
return [security_group["name"]]
def _build_heat_nics(self, nics):
ifaces = []
ports = []
if nics:
for idx, nic in enumerate(nics):
iface_id = nic.get('port-id')
if iface_id:
ifaces.append(iface_id)
continue
net_id = nic.get('net-id')
if net_id:
port = {}
port['name'] = "Port%s" % idx
port['net_id'] = net_id
fixed_ip = nic.get('v4-fixed-ip')
if fixed_ip:
port['fixed_ip'] = fixed_ip
ports.append(port)
ifaces.append("{Ref: Port%s}" % idx)
return ifaces, ports
class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin):
"""

View File

@ -18,6 +18,16 @@ Parameters:
TenantId:
Type: String
Resources:
{% for port in ports %}
{{ port.name }}:
Type: OS::Neutron::Port
Properties:
network_id: "{{ port.net_id }}"
security_groups: [{Ref: MySqlDbaasSG}]
{% if port.fixed_ip %}
fixed_ips: [{"ip_address": "{{ port.fixed_ip }}"}]
{% endif %}
{% endfor %}
BaseInstance:
Type: AWS::EC2::Instance
Metadata:
@ -38,7 +48,11 @@ Resources:
ImageId: {Ref: ImageId}
InstanceType: {Ref: Flavor}
AvailabilityZone: {Ref: AvailabilityZone}
SecurityGroups : [{Ref: MySqlDbaasSG}]
{% if ifaces %}
NetworkInterfaces: [{{ ifaces|join(', ') }}]
{% else %}
SecurityGroups: [{Ref: MySqlDbaasSG}]
{% endif %}
UserData:
Fn::Base64:
Fn::Join:

View File

@ -387,6 +387,15 @@ class CreateInstance(object):
None, databases, availability_zone="NOOP")
assert_equal(400, dbaas.last_http_code)
@test(enabled=not FAKE)
def test_create_with_bad_nics(self):
instance_name = "instance-failure-with-bad-nics"
databases = []
assert_raises(exceptions.BadRequest, dbaas.instances.create,
instance_name, instance_info.dbaas_flavor_href,
None, databases, nics="BAD")
assert_equal(400, dbaas.last_http_code)
@test(enabled=VOLUME_SUPPORT)
def test_create_failure_with_empty_volume(self):
instance_name = "instance-failure-with-no-volume-size"

View File

@ -260,7 +260,7 @@ class FakeServers(object):
def create(self, name, image_id, flavor_ref, files=None, userdata=None,
block_device_mapping=None, volume=None, security_groups=None,
availability_zone=None):
availability_zone=None, nics=None):
id = "FAKE_%s" % uuid.uuid4()
if volume:
volume = self.volumes.create(volume['size'], volume['name'],

View File

@ -48,7 +48,8 @@ class fake_Server:
class fake_ServerManager:
def create(self, name, image_id, flavor_id, files, userdata,
security_groups, block_device_mapping, availability_zone=None):
security_groups, block_device_mapping, availability_zone=None,
nics=None):
server = fake_Server()
server.id = "server_id"
server.name = name
@ -59,6 +60,7 @@ class fake_ServerManager:
server.security_groups = security_groups
server.block_device_mapping = block_device_mapping
server.availability_zone = availability_zone
server.nics = nics
return server
@ -180,33 +182,33 @@ class FreshInstanceTasksTest(testtools.TestCase):
when(taskmanager_models.CONF).get("cloudinit_location").thenReturn(
cloudinit_location)
server = self.freshinstancetasks._create_server(
None, None, None, datastore_manager, None, None)
None, None, None, datastore_manager, None, None, None)
self.assertEqual(server.userdata, self.userdata)
def test_create_instance_guestconfig(self):
when(taskmanager_models.CONF).get("guest_config").thenReturn(
self.guestconfig)
server = self.freshinstancetasks._create_server(
None, None, None, "test", None, None)
None, None, None, "test", None, None, None)
self.assertTrue('/etc/trove-guestagent.conf' in server.files)
self.assertEqual(server.files['/etc/trove-guestagent.conf'],
self.guestconfig_content)
def test_create_instance_with_az_kwarg(self):
server = self.freshinstancetasks._create_server(
None, None, None, None, None, availability_zone='nova')
None, None, None, None, None, availability_zone='nova', nics=None)
self.assertIsNotNone(server)
def test_create_instance_with_az(self):
server = self.freshinstancetasks._create_server(
None, None, None, None, None, 'nova')
None, None, None, None, None, 'nova', None)
self.assertIsNotNone(server)
def test_create_instance_with_az_none(self):
server = self.freshinstancetasks._create_server(
None, None, None, None, None, None)
None, None, None, None, None, None, None)
self.assertIsNotNone(server)