diff options
Diffstat (limited to 'src/resource_inventory')
-rw-r--r-- | src/resource_inventory/admin.py | 5 | ||||
-rw-r--r-- | src/resource_inventory/idf_templater.py | 151 | ||||
-rw-r--r-- | src/resource_inventory/migrations/0009_auto_20190315_1757.py | 73 | ||||
-rw-r--r-- | src/resource_inventory/migrations/0010_auto_20190430_1405.py | 54 | ||||
-rw-r--r-- | src/resource_inventory/models.py | 77 | ||||
-rw-r--r-- | src/resource_inventory/pdf_templater.py | 53 | ||||
-rw-r--r-- | src/resource_inventory/resource_manager.py | 79 | ||||
-rw-r--r-- | src/resource_inventory/urls.py | 5 | ||||
-rw-r--r-- | src/resource_inventory/views.py | 17 |
9 files changed, 441 insertions, 73 deletions
diff --git a/src/resource_inventory/admin.py b/src/resource_inventory/admin.py index e063cc0..7ff510b 100644 --- a/src/resource_inventory/admin.py +++ b/src/resource_inventory/admin.py @@ -32,7 +32,8 @@ from resource_inventory.models import ( OPNFVConfig, OPNFVRole, Image, - HostConfiguration + HostConfiguration, + RemoteInfo ) profiles = [HostProfile, InterfaceProfile, DiskProfile, CpuProfile, RamProfile] @@ -47,6 +48,6 @@ physical = [Host, Interface, Network, Vlan, ResourceBundle] admin.site.register(physical) -config = [Scenario, Installer, Opsys, ConfigBundle, OPNFVConfig, OPNFVRole, Image, HostConfiguration] +config = [Scenario, Installer, Opsys, ConfigBundle, OPNFVConfig, OPNFVRole, Image, HostConfiguration, RemoteInfo] admin.site.register(config) diff --git a/src/resource_inventory/idf_templater.py b/src/resource_inventory/idf_templater.py new file mode 100644 index 0000000..bf6eda0 --- /dev/null +++ b/src/resource_inventory/idf_templater.py @@ -0,0 +1,151 @@ +############################################################################## +# Copyright (c) 2019 Parker Berberian, Sawyer Bergeron, and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.template.loader import render_to_string + +from account.models import PublicNetwork + +from resource_inventory.models import Vlan + + +class IDFTemplater: + """ + Utility class to create a full IDF yaml file + """ + net_names = ["admin", "mgmt", "private", "public"] + bridge_names = { + "admin": "br-admin", + "mgmt": "br-mgmt", + "private": "br-private", + "public": "br-public" + } + + def __init__(self): + self.networks = {} + for i, name in enumerate(self.net_names): + self.networks[name] = { + "name": name, + "vlan": -1, + "interface": i, + "ip": "10.250." + str(i) + ".0", + "netmask": 24 + } + + def makeIDF(self, booking): + """ + fills the installer descriptor file template with info about the resource + """ + template = "dashboard/idf.yaml" + info = {} + info['version'] = "0.1" + info['net_config'] = self.get_net_config(booking) + info['fuel'] = self.get_fuel_config(booking) + + return render_to_string(template, context=info) + + def get_net_config(self, booking): + net_config = {} + try: + net_config['oob'] = self.get_oob_net(booking) + except Exception: + net_config['oob'] = {} + try: + net_config['public'] = self.get_public_net(booking) + except Exception: + net_config['public'] = {} + + for net in [net for net in self.net_names if net != "public"]: + try: + net_config[net] = self.get_single_net_config(booking, net) + except Exception: + net_config[net] = {} + + return net_config + + def get_public_net(self, booking): + public = {} + config = booking.opnfv_config + public_role = config.networks.get(name="public") + public_vlan = Vlan.objects.filter(network=public_role.network).first() + public_network = PublicNetwork.objects.get(vlan=public_vlan.vlan_id, lab=booking.lab) + self.networks['public']['vlan'] = public_vlan.vlan_id + public['interface'] = self.networks['public']['interface'] + public['vlan'] = public_network.vlan # untagged?? + public['network'] = public_network.cidr.split("/")[0] + public['mask'] = public_network.cidr.split("/")[1] + # public['ip_range'] = 4 # necesary? + public['gateway'] = public_network.gateway + public['dns'] = ["1.1.1.1", "8.8.8.8"] + + return public + + def get_oob_net(self, booking): + net = {} + hosts = booking.resource.hosts.all() + addrs = [host.remote_management.address for host in hosts] + net['ip_range'] = ",".join(addrs) + net['vlan'] = "native" + return net + + def get_single_net_config(self, booking, net_name): + config = booking.opnfv_config + role = config.networks.get(name=net_name) + vlan = Vlan.objects.filter(network=role.network).first() + self.networks[net_name]['vlan'] = vlan.vlan_id + net = {} + net['interface'] = self.networks[net_name]['interface'] + net['vlan'] = vlan.vlan_id + net['network'] = self.networks[net_name]['ip'] + net['mask'] = self.networks[net_name]['netmask'] + + return net + + def get_fuel_config(self, booking): + fuel = {} + fuel['jumphost'] = {} + try: + fuel['jumphost']['bridges'] = self.get_fuel_bridges() + except Exception: + fuel['jumphost']['bridges'] = {} + + fuel['network'] = {} + try: + fuel['network']['nodes'] = self.get_fuel_nodes(booking) + except Exception: + fuel['network']['nodes'] = {} + + return fuel + + def get_fuel_bridges(self): + return self.bridge_names + + def get_fuel_nodes(self, booking): + jumphost = booking.opnfv_config.host_opnfv_config.get( + role__name__iexact="jumphost" + ) + hosts = booking.resource.hosts.exclude(pk=jumphost.pk) + nodes = [] + for host in hosts: + node = {} + ordered_interfaces = self.get_node_interfaces(host) + node['interfaces'] = [iface['name'] for iface in ordered_interfaces] + node['bus_addrs'] = [iface['bus'] for iface in ordered_interfaces] + nodes.append(node) + + return nodes + + def get_node_interfaces(self, node): + # TODO: this should sync with pdf ordering + interfaces = [] + + for iface in node.interfaces.all(): + interfaces.append({"name": iface.name, "bus": iface.bus_address}) + + return interfaces diff --git a/src/resource_inventory/migrations/0009_auto_20190315_1757.py b/src/resource_inventory/migrations/0009_auto_20190315_1757.py new file mode 100644 index 0000000..92ed0e9 --- /dev/null +++ b/src/resource_inventory/migrations/0009_auto_20190315_1757.py @@ -0,0 +1,73 @@ +# Generated by Django 2.1 on 2019-03-15 17:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0008_host_remote_management'), + ] + + operations = [ + migrations.CreateModel( + name='NetworkConnection', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vlan_is_tagged', models.BooleanField()), + ], + ), + migrations.CreateModel( + name='NetworkRole', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.RemoveField( + model_name='genericinterface', + name='vlans', + ), + migrations.RemoveField( + model_name='network', + name='vlan_id', + ), + migrations.AddField( + model_name='network', + name='bundle', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='networks', to='resource_inventory.GenericResourceBundle'), + preserve_default=False, + ), + migrations.AddField( + model_name='network', + name='is_public', + field=models.BooleanField(default=False), + preserve_default=False, + ), + migrations.AddField( + model_name='vlan', + name='network', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.Network'), + ), + migrations.AddField( + model_name='networkrole', + name='network', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Network'), + ), + migrations.AddField( + model_name='networkconnection', + name='network', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Network'), + ), + migrations.AddField( + model_name='genericinterface', + name='connections', + field=models.ManyToManyField(to='resource_inventory.NetworkConnection'), + ), + migrations.AddField( + model_name='opnfvconfig', + name='networks', + field=models.ManyToManyField(to='resource_inventory.NetworkRole'), + ), + ] diff --git a/src/resource_inventory/migrations/0010_auto_20190430_1405.py b/src/resource_inventory/migrations/0010_auto_20190430_1405.py new file mode 100644 index 0000000..3823eaf --- /dev/null +++ b/src/resource_inventory/migrations/0010_auto_20190430_1405.py @@ -0,0 +1,54 @@ +# Generated by Django 2.1 on 2019-04-30 14:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0009_auto_20190315_1757'), + ] + + operations = [ + migrations.CreateModel( + name='HostOPNFVConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.RemoveField( + model_name='hostconfiguration', + name='opnfvRole', + ), + migrations.AddField( + model_name='hostconfiguration', + name='is_head_node', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='opnfvconfig', + name='description', + field=models.CharField(blank=True, default='', max_length=600), + ), + migrations.AddField( + model_name='opnfvconfig', + name='name', + field=models.CharField(blank=True, default='', max_length=300), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='host_config', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_config', to='resource_inventory.HostConfiguration'), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='opnfv_config', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_config', to='resource_inventory.OPNFVConfig'), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='role', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_configs', to='resource_inventory.OPNFVRole'), + ), + ] diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index bdc1f5d..b9f2c44 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -105,26 +105,6 @@ class RamProfile(models.Model): return str(self.amount) + "G for " + str(self.host) -# Networking -- located here due to import order requirements -class Network(models.Model): - id = models.AutoField(primary_key=True) - vlan_id = models.IntegerField() - name = models.CharField(max_length=100) - - def __str__(self): - return self.name - - -class Vlan(models.Model): - id = models.AutoField(primary_key=True) - vlan_id = models.IntegerField() - tagged = models.BooleanField() - public = models.BooleanField(default=False) - - def __str__(self): - return str(self.vlan_id) + ("_T" if self.tagged else "") - - # Generic resource templates class GenericResourceBundle(models.Model): id = models.AutoField(primary_key=True) @@ -145,6 +125,32 @@ class GenericResourceBundle(models.Model): return self.name +class Network(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=100) + bundle = models.ForeignKey(GenericResourceBundle, on_delete=models.CASCADE, related_name="networks") + is_public = models.BooleanField() + + def __str__(self): + return self.name + + +class NetworkConnection(models.Model): + network = models.ForeignKey(Network, on_delete=models.CASCADE) + vlan_is_tagged = models.BooleanField() + + +class Vlan(models.Model): + id = models.AutoField(primary_key=True) + vlan_id = models.IntegerField() + tagged = models.BooleanField() + public = models.BooleanField(default=False) + network = models.ForeignKey(Network, on_delete=models.DO_NOTHING, null=True) + + def __str__(self): + return str(self.vlan_id) + ("_T" if self.tagged else "") + + class GenericResource(models.Model): bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.CASCADE) hostname_validchars = RegexValidator(regex=r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))', message="Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)") @@ -185,17 +191,14 @@ class ResourceBundle(models.Model): return "instance of " + str(self.template) def get_host(self, role="Jumphost"): - return Host.objects.filter(bundle=self, config__opnfvRole__name=role).first() - - -# Networking + return Host.objects.filter(bundle=self, config__is_head_node=True).first() # should only ever be one, but it is not an invariant in the models class GenericInterface(models.Model): id = models.AutoField(primary_key=True) - vlans = models.ManyToManyField(Vlan) profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE) host = models.ForeignKey(GenericHost, on_delete=models.CASCADE, related_name='generic_interfaces') + connections = models.ManyToManyField(NetworkConnection) def __str__(self): return "type " + str(self.profile) + " on host " + str(self.host) @@ -227,6 +230,11 @@ class Opsys(models.Model): return self.name +class NetworkRole(models.Model): + name = models.CharField(max_length=100) + network = models.ForeignKey(Network, on_delete=models.CASCADE) + + class ConfigBundle(models.Model): id = models.AutoField(primary_key=True) owner = models.ForeignKey(User, on_delete=models.CASCADE) @@ -243,6 +251,9 @@ class OPNFVConfig(models.Model): installer = models.ForeignKey(Installer, on_delete=models.CASCADE) scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE) bundle = models.ForeignKey(ConfigBundle, related_name="opnfv_config", on_delete=models.CASCADE) + networks = models.ManyToManyField(NetworkRole) + name = models.CharField(max_length=300, blank=True, default="") + description = models.CharField(max_length=600, blank=True, default="") def __str__(self): return "OPNFV job with " + str(self.installer) + " and " + str(self.scenario) @@ -288,12 +299,18 @@ class HostConfiguration(models.Model): host = models.ForeignKey(GenericHost, related_name="configuration", on_delete=models.CASCADE) image = models.ForeignKey(Image, on_delete=models.PROTECT) bundle = models.ForeignKey(ConfigBundle, related_name="hostConfigurations", null=True, on_delete=models.CASCADE) - opnfvRole = models.ForeignKey(OPNFVRole, on_delete=models.SET(get_sentinal_opnfv_role)) + is_head_node = models.BooleanField(default=False) def __str__(self): return "config with " + str(self.host) + " and image " + str(self.image) +class HostOPNFVConfig(models.Model): + role = models.ForeignKey(OPNFVRole, related_name="host_opnfv_configs", on_delete=models.CASCADE) + host_config = models.ForeignKey(HostConfiguration, related_name="host_opnfv_config", on_delete=models.CASCADE) + opnfv_config = models.ForeignKey(OPNFVConfig, related_name="host_opnfv_config", on_delete=models.CASCADE) + + class RemoteInfo(models.Model): address = models.CharField(max_length=15) mac_address = models.CharField(max_length=17) @@ -344,3 +361,11 @@ class Interface(models.Model): def __str__(self): return self.mac_address + " on host " + str(self.host) + + +class OPNFV_SETTINGS(): + """ + This is a static configuration class + """ + # all the required network types in PDF/IDF spec + NETWORK_ROLES = ["public", "private", "admin", "mgmt"] diff --git a/src/resource_inventory/pdf_templater.py b/src/resource_inventory/pdf_templater.py index a50f04c..2302530 100644 --- a/src/resource_inventory/pdf_templater.py +++ b/src/resource_inventory/pdf_templater.py @@ -19,15 +19,15 @@ class PDFTemplater: """ @classmethod - def makePDF(cls, resource): + def makePDF(cls, booking): """ fills the pod descriptor file template with info about the resource """ template = "dashboard/pdf.yaml" info = {} - info['details'] = cls.get_pdf_details(resource) - info['jumphost'] = cls.get_pdf_jumphost(resource) - info['nodes'] = cls.get_pdf_nodes(resource) + info['details'] = cls.get_pdf_details(booking.resource) + info['jumphost'] = cls.get_pdf_jumphost(booking) + info['nodes'] = cls.get_pdf_nodes(booking) return render_to_string(template, context=info) @@ -63,26 +63,40 @@ class PDFTemplater: return details @classmethod - def get_pdf_jumphost(cls, resource): + def get_jumphost(cls, booking): + jumphost = None + if booking.opnfv_config: + jumphost_opnfv_config = booking.opnfv_config.host_opnfv_config.get( + role__name__iexact="jumphost" + ) + jumphost = booking.resource.hosts.get(config=jumphost_opnfv_config.host_config) + else: # if there is no opnfv config, use headnode + jumphost = Host.objects.filter( + bundle=booking.resource, + config__is_head_node=True + ).first() + + return jumphost + + @classmethod + def get_pdf_jumphost(cls, booking): """ returns a dict of all the info for the "jumphost" section """ - jumphost = Host.objects.get(bundle=resource, config__opnfvRole__name__iexact="jumphost") + jumphost = cls.get_jumphost(booking) jumphost_info = cls.get_pdf_host(jumphost) - remote_params = jumphost_info['remote_management'] # jumphost has extra block not in normal hosts - remote_params.pop("address") - remote_params.pop("mac_address") - jumphost_info['remote_params'] = remote_params jumphost_info['os'] = jumphost.config.image.os.name return jumphost_info @classmethod - def get_pdf_nodes(cls, resource): + def get_pdf_nodes(cls, booking): """ returns a list of all the "nodes" (every host except jumphost) """ pdf_nodes = [] - nodes = Host.objects.filter(bundle=resource).exclude(config__opnfvRole__name__iexact="jumphost") + nodes = set(Host.objects.filter(bundle=booking.resource)) + nodes.discard(cls.get_jumphost(booking)) + for node in nodes: pdf_nodes.append(cls.get_pdf_host(node)) @@ -105,7 +119,7 @@ class PDFTemplater: for interface in host.interfaces.all(): host_info['interfaces'].append(cls.get_pdf_host_iface(interface)) - host_info['remote_management'] = cls.get_pdf_host_remote_management(host) + host_info['remote'] = cls.get_pdf_host_remote_management(host) return host_info @@ -168,11 +182,12 @@ class PDFTemplater: """ gives the remote params of the host """ + man = host.remote_management mgmt = {} - mgmt['address'] = "I dunno" - mgmt['mac_address'] = "I dunno" - mgmt['pass'] = "I dunno" - mgmt['type'] = "I dunno" - mgmt['user'] = "I dunno" - mgmt['versions'] = ["I dunno"] + mgmt['address'] = man.address + mgmt['mac_address'] = man.mac_address + mgmt['pass'] = man.password + mgmt['type'] = man.management_type + mgmt['user'] = man.user + mgmt['versions'] = [man.versions] return mgmt diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py index 52b0055..652e4e3 100644 --- a/src/resource_inventory/resource_manager.py +++ b/src/resource_inventory/resource_manager.py @@ -14,7 +14,14 @@ from dashboard.exceptions import ( ResourceProvisioningException, ModelValidationException, ) -from resource_inventory.models import Host, HostConfiguration, ResourceBundle, HostProfile +from resource_inventory.models import ( + Host, + HostConfiguration, + ResourceBundle, + HostProfile, + Network, + Vlan +) class ResourceManager: @@ -66,55 +73,71 @@ class ResourceManager: self.releaseHost(host) resourceBundle.delete() - def convertResourceBundle(self, genericResourceBundle, lab=None, config=None): + def get_vlans(self, genericResourceBundle): + networks = {} + vlan_manager = genericResourceBundle.lab.vlan_manager + for network in genericResourceBundle.networks.all(): + if network.is_public: + public_net = vlan_manager.get_public_vlan() + vlan_manager.reserve_public_vlan(public_net.vlan) + networks[network.name] = public_net.vlan + else: + vlan = vlan_manager.get_vlan() + vlan_manager.reserve_vlans(vlan) + networks[network.name] = vlan + return networks + + def convertResourceBundle(self, genericResourceBundle, config=None): """ Takes in a GenericResourceBundle and 'converts' it into a ResourceBundle """ - resource_bundle = ResourceBundle() - resource_bundle.template = genericResourceBundle - resource_bundle.save() - - hosts = genericResourceBundle.getHosts() - - # current supported case: user creating new booking - # currently unsupported: editing existing booking - + resource_bundle = ResourceBundle.objects.create(template=genericResourceBundle) + generic_hosts = genericResourceBundle.getHosts() physical_hosts = [] - for host in hosts: + vlan_map = self.get_vlans(genericResourceBundle) + + for generic_host in generic_hosts: host_config = None if config: - host_config = HostConfiguration.objects.get(bundle=config, host=host) + host_config = HostConfiguration.objects.get(bundle=config, host=generic_host) try: - physical_host = self.acquireHost(host, genericResourceBundle.lab.name) + physical_host = self.acquireHost(generic_host, genericResourceBundle.lab.name) except ResourceAvailabilityException: - self.fail_acquire(physical_hosts) + self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle) raise ResourceAvailabilityException("Could not provision hosts, not enough available") try: physical_host.bundle = resource_bundle - physical_host.template = host + physical_host.template = generic_host physical_host.config = host_config physical_hosts.append(physical_host) - self.configureNetworking(physical_host) + self.configureNetworking(physical_host, vlan_map) except Exception: - self.fail_acquire(physical_hosts) + self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle) raise ResourceProvisioningException("Network configuration failed.") try: physical_host.save() except Exception: - self.fail_acquire(physical_hosts) + self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle) raise ModelValidationException("Saving hosts failed") return resource_bundle - def configureNetworking(self, host): + def configureNetworking(self, host, vlan_map): generic_interfaces = list(host.template.generic_interfaces.all()) for int_num, physical_interface in enumerate(host.interfaces.all()): generic_interface = generic_interfaces[int_num] physical_interface.config.clear() - for vlan in generic_interface.vlans.all(): - physical_interface.config.add(vlan) + for connection in generic_interface.connections.all(): + physical_interface.config.add( + Vlan.objects.create( + vlan_id=vlan_map[connection.network.name], + tagged=connection.vlan_is_tagged, + public=connection.network.is_public, + network=connection.network + ) + ) # private interface def acquireHost(self, genericHost, labName): @@ -136,6 +159,16 @@ class ResourceManager: host.booked = False host.save() - def fail_acquire(self, hosts): + def releaseNetworks(self, grb, vlan_manager, vlans): + for net_name, vlan_id in vlans.items(): + net = Network.objects.get(name=net_name, bundle=grb) + if(net.is_public): + vlan_manager.release_public_vlan(vlan_id) + else: + vlan_manager.release_vlans(vlan_id) + + def fail_acquire(self, hosts, vlans, grb): + vlan_manager = grb.lab.vlan_manager + self.releaseNetworks(grb, vlan_manager, vlans) for host in hosts: self.releaseHost(host) diff --git a/src/resource_inventory/urls.py b/src/resource_inventory/urls.py index 4e159ba..a72871b 100644 --- a/src/resource_inventory/urls.py +++ b/src/resource_inventory/urls.py @@ -25,10 +25,11 @@ Including another URLconf 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url -from resource_inventory.views import HostView +from resource_inventory.views import HostView, hostprofile_detail_view app_name = "resource" urlpatterns = [ - url(r'^hosts$', HostView.as_view(), name='hosts') + url(r'^hosts$', HostView.as_view(), name='hosts'), + url(r'^profiles/(?P<hostprofile_id>.+)/$', hostprofile_detail_view, name='host_detail'), ] diff --git a/src/resource_inventory/views.py b/src/resource_inventory/views.py index 2937bd7..8c3d899 100644 --- a/src/resource_inventory/views.py +++ b/src/resource_inventory/views.py @@ -9,8 +9,10 @@ from django.views.generic import TemplateView +from django.shortcuts import get_object_or_404 +from django.shortcuts import render -from resource_inventory.models import Host +from resource_inventory.models import HostProfile, Host class HostView(TemplateView): @@ -21,3 +23,16 @@ class HostView(TemplateView): hosts = Host.objects.filter(working=True) context.update({'hosts': hosts, 'title': "Hardware Resources"}) return context + + +def hostprofile_detail_view(request, hostprofile_id): + hostprofile = get_object_or_404(HostProfile, id=hostprofile_id) + + return render( + request, + "resource/hostprofile_detail.html", + { + 'title': "Host Type: " + str(hostprofile.name), + 'hostprofile': hostprofile + } + ) |