diff options
author | Matt Wagner <matt.wagner@redhat.com> | 2013-07-19 10:13:04 -0400 |
---|---|---|
committer | Tomas Sedovic <tomas@sedovic.cz> | 2013-08-01 16:18:57 +0200 |
commit | 410f602735987c791c3a370d68372332c3e30e2f (patch) | |
tree | be46618a66ee5ded0ca3b67c7248c0d324b41eb8 | |
parent | 8cdd8b24337a2e4843a51d6968a4cf6dab822136 (diff) | |
download | tuskar-ui-410f602735987c791c3a370d68372332c3e30e2f.tar.gz |
Early, untested support for Node creation
This remains a work in progress.
jprovazn update:
this is and updated version of matt's patch (I had to squash them
because gerrit would send both patches separately).
With this update, nodes creation should work, uou have to do some setup
to be able to talk to nova baremetal:
1) ssh tunnels (local ports must not override local openstack ports):
ssh -NfL 5001:192.0.2.2:5000 demo@example.com
ssh -NfL 9774:192.0.2.2:8774 demo@example.com
2) set NOVA_BAREMETAL_CREDS in openstack_dashboard/local/local_settings.py:
these are creds for the nova baremetal openstack.
bypass_url has admin's tenant id at the end, you can check/get tenant id by running
"keystone --debug --os-username=admin --os-password=unset --os-tenant-name=admin --os-auth-url=http://192.0.2.2:5000/v2.0/ endpoint-list"
on a test/demo server
Change-Id: I0d5c4860fd5fca2926c8161d0c65d7b1e001ebb9
8 files changed, 107 insertions, 40 deletions
diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index ba42c9b0..35535183 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -30,8 +30,10 @@ from django.utils.translation import ugettext_lazy as _ from novaclient.v1_1 import client as nova_client from novaclient.v1_1.contrib.list_extensions import ListExtManager from novaclient.v1_1 import security_group_rules as nova_rules +from novaclient.v1_1.contrib import baremetal from novaclient.v1_1.security_groups import SecurityGroup as NovaSecurityGroup from novaclient.v1_1.servers import REBOOT_HARD +from novaclient.v1_1.servers import REBOOT_SOFT from horizon.conf import HORIZON_CONFIG from horizon.utils.memoized import memoized diff --git a/openstack_dashboard/api/tuskar.py b/openstack_dashboard/api/tuskar.py index 2ebdeb45..46841017 100644 --- a/openstack_dashboard/api/tuskar.py +++ b/openstack_dashboard/api/tuskar.py @@ -27,12 +27,13 @@ from horizon import exceptions from tuskarclient.v1 import client as tuskar_client -from openstack_dashboard.api import base +from openstack_dashboard.api import base, nova import openstack_dashboard.dashboards.infrastructure.models as dummymodels LOG = logging.getLogger(__name__) TUSKAR_ENDPOINT_URL = getattr(settings, 'TUSKAR_ENDPOINT_URL') +NOVA_BAREMETAL_CREDS = getattr(settings, 'NOVA_BAREMETAL_CREDS') # FIXME: request isn't used right in the tuskar client right now, but looking @@ -137,11 +138,26 @@ class Node(StringIdAPIResourceWrapper): """Wrapper for the Node object returned by the dummy model. """ - _attrs = ['name', 'mac_address', 'ip_address', 'status', 'usage', 'rack'] + _attrs = ['id', 'pm_address', 'cpus', 'memory_mb', 'service_host', + 'local_gb'] + + @classmethod + def manager(cls): + nc = nova.nova_client.Client( + NOVA_BAREMETAL_CREDS['user'], + NOVA_BAREMETAL_CREDS['password'], + NOVA_BAREMETAL_CREDS['tenant'], + auth_url=NOVA_BAREMETAL_CREDS['auth_url'], + bypass_url=NOVA_BAREMETAL_CREDS['bypass_url']) + return nova.baremetal.BareMetalNodeManager(nc) @classmethod def get(cls, request, node_id): - return cls(dummymodels.Node.objects.get(id=node_id)) + return cls(cls.manager().get(node_id)) + + @classmethod + def list(cls, request): + return cls.manager().list() @classmethod def list_unracked(cls, request): @@ -292,9 +308,16 @@ class Node(StringIdAPIResourceWrapper): self._alerts = [Alert(a) for a in dummymodels.Alert.objects .filter(object_type='node') - .filter(object_id=int(self.id))] + .filter(object_id=str(self.id))] return self._alerts + @property + def mac_address(self): + try: + return self._apiresource.interfaces[0]['address'] + except: + return None + class Rack(StringIdAPIResourceWrapper): """Wrapper for the Rack object returned by the @@ -364,10 +387,10 @@ class Rack(StringIdAPIResourceWrapper): ## fetch nodes from nova baremetal @property def list_nodes(self): - return [] - #if not hasattr(self, '_nodes'): - # self._nodes = [Node(h) for h in self._apiresource.node_set.all()] - #return self._nodes + if not hasattr(self, '_nodes'): + self._nodes = [Node.get(None, node['id']) for node in ( + self._apiresource.nodes)] + return self._nodes def nodes_count(self): return len(self._apiresource.nodes) diff --git a/openstack_dashboard/dashboards/infrastructure/fixtures/initial_data.json b/openstack_dashboard/dashboards/infrastructure/fixtures/initial_data.json index 5cbd8e2c..281af6e4 100644 --- a/openstack_dashboard/dashboards/infrastructure/fixtures/initial_data.json +++ b/openstack_dashboard/dashboards/infrastructure/fixtures/initial_data.json @@ -88,7 +88,7 @@ {"pk": 53, "model": "infrastructure.capacity", "fields": {"value": 160, "unit": "GB", "object_id": 7, "content_type": ["infrastructure", "flavortemplate"], "name": "storage"}}, {"pk": 54, "model": "infrastructure.capacity", "fields": {"value": 0, "unit": "GB", "object_id": 7, "content_type": ["infrastructure", "flavortemplate"], "name": "ephemeral_disk"}}, {"pk": 55, "model": "infrastructure.capacity", "fields": {"value": 0, "unit": "MB", "object_id": 7, "content_type": ["infrastructure", "flavortemplate"], "name": "swap_disk"}}, - {"pk": 1, "model": "infrastructure.alert", "fields": {"message": "Switch is not accessible.", "object_id": 1, "object_type": "rack", "time": "2011-09-01T13:20:30+03:00"}}, - {"pk": 2, "model": "infrastructure.alert", "fields": {"message": "Nova service is not running.", "object_id": 1, "object_type": "node", "time": "2011-09-01T13:20:30+03:00"}}, - {"pk": 3, "model": "infrastructure.alert", "fields": {"message": "Disk usage is over 90%.", "object_id": 1, "object_type": "node", "time": "2011-09-01T13:20:30+03:00"}} + {"pk": 1, "model": "infrastructure.alert", "fields": {"message": "Switch is not accessible.", "object_id": "1", "object_type": "rack", "time": "2011-09-01T13:20:30+03:00"}}, + {"pk": 2, "model": "infrastructure.alert", "fields": {"message": "Nova service is not running.", "object_id": "1", "object_type": "node", "time": "2011-09-01T13:20:30+03:00"}}, + {"pk": 3, "model": "infrastructure.alert", "fields": {"message": "Disk usage is over 90%.", "object_id": "1", "object_type": "node", "time": "2011-09-01T13:20:30+03:00"}} ] diff --git a/openstack_dashboard/dashboards/infrastructure/models.py b/openstack_dashboard/dashboards/infrastructure/models.py index 513e463d..8097e5b1 100644 --- a/openstack_dashboard/dashboards/infrastructure/models.py +++ b/openstack_dashboard/dashboards/infrastructure/models.py @@ -34,7 +34,7 @@ class Alert(models.Model): class Meta: db_table = 'infrastructure_alerts' - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=50) object_type = models.CharField(max_length=20) message = models.CharField(max_length=250) time = models.DateTimeField() diff --git a/openstack_dashboard/dashboards/infrastructure/resource_management/nodes/tables.py b/openstack_dashboard/dashboards/infrastructure/resource_management/nodes/tables.py index 0b54922d..f1b19783 100644 --- a/openstack_dashboard/dashboards/infrastructure/resource_management/nodes/tables.py +++ b/openstack_dashboard/dashboards/infrastructure/resource_management/nodes/tables.py @@ -39,12 +39,12 @@ class NodesTable(tables.DataTable): ("active", True), ("error", False), ) - name = tables.Column("name", + service_host = tables.Column("service_host", link=("horizon:infrastructure:" "resource_management:nodes:detail"), verbose_name=_("Name")) mac_address = tables.Column("mac_address", verbose_name=_("MAC Address")) - ip_address = tables.Column("ip_address", verbose_name=_("IP Address")) + pm_address = tables.Column("pm_address", verbose_name=_("IP Address")) status = tables.Column("status", verbose_name=_("Status"), status=True, diff --git a/openstack_dashboard/dashboards/infrastructure/resource_management/racks/views.py b/openstack_dashboard/dashboards/infrastructure/resource_management/racks/views.py index 2ab8bf82..b3bf1872 100644 --- a/openstack_dashboard/dashboards/infrastructure/resource_management/racks/views.py +++ b/openstack_dashboard/dashboards/infrastructure/resource_management/racks/views.py @@ -67,11 +67,10 @@ class EditView(workflows.WorkflowView): def get_initial(self): obj = api.tuskar.Rack.get(self.request, self.kwargs['rack_id']) - mac_str = "\n".join([x.mac_address for x in obj.list_nodes]) + # mac_str = "\n".join([x.mac_address for x in obj.list_nodes]) return {'name': obj.name, 'resource_class_id': obj.resource_class_id, 'location': obj.location, 'subnet': obj.subnet, - 'state': obj.state, 'node_macs': mac_str, - 'rack_id': self.kwargs['rack_id']} + 'state': obj.state, 'rack_id': self.kwargs['rack_id']} class DetailEditView(EditView): diff --git a/openstack_dashboard/dashboards/infrastructure/resource_management/racks/workflows.py b/openstack_dashboard/dashboards/infrastructure/resource_management/racks/workflows.py index d415ee0d..0499dc86 100644 --- a/openstack_dashboard/dashboards/infrastructure/resource_management/racks/workflows.py +++ b/openstack_dashboard/dashboards/infrastructure/resource_management/racks/workflows.py @@ -26,28 +26,52 @@ import re class NodeCreateAction(workflows.Action): - node_macs = forms.CharField(label=_("MAC Addresses"), - widget=forms.Textarea(attrs={'rows': 12, 'cols': 20}), - required=False) + # node_macs = forms.CharField(label=_("MAC Addresses"), + # widget=forms.Textarea(attrs={'rows': 12, 'cols': 20}), + # required=False) + + node_name = forms.CharField(label="Name", required=True) + prov_mac_address = forms.CharField(label=("MAC Address"), + required=True) + + # Hardware Specifications + cpus = forms.CharField(label="CPUs", required=True) + memory_mb = forms.CharField(label="Memory", required=True) + local_gb = forms.CharField(label="Local Disk (GB)", required=True) + + # Power Management + pm_address = forms.CharField(label="IP Address", required=True) + pm_user = forms.CharField(label="User", required=True) + pm_password = forms.CharField(label="Password", + required=True, + widget=forms.PasswordInput( + render_value=False)) + + # Access + terminal_port = forms.CharField(label="Terminal Port", required=False) class Meta: name = _("Nodes") -# mawagner FIXME - We presently disable this box, but leave the form. -# We need to decide if this should be supported, or whether users should -# be required to use the table view (to be implemented). -# NOTE: The form input is disabled, but the input is also ignored -# even if it were present. -class NodeEditAction(workflows.Action): - node_macs = forms.CharField(label=_("MAC Addresses"), - widget=forms.Textarea(attrs={'rows': 12, 'cols': 20, - 'readonly': 'readonly'}), - required=False) +# mawagner FIXME - For the demo, all we can really do is edit the one +# associated node. That's very much _not_ what this form is actually +# about, though. +class NodeEditAction(NodeCreateAction): class Meta: name = _("Nodes") + # FIXME: mawagner - This is all for debugging. The idea is to fetch + # the first node and display it in the form; the latter part needs + # implementation. This also needs error handling; right now for testing + # I want to let it fail, but don't commit like that! :) + def __init__(self, request, *args, **kwargs): + super(NodeEditAction, self).__init__(request, *args, **kwargs) + rack_id = self.initial['rack_id'] + rack = api.tuskar.Rack.get(request, rack_id) + nodes = rack.list_nodes + class RackCreateInfoAction(workflows.Action): name = forms.RegexField(label=_("Name"), @@ -121,17 +145,19 @@ class EditRackInfo(CreateRackInfo): class CreateNodes(workflows.Step): action_class = NodeCreateAction - contributes = ('node_macs',) + contributes = ('node_name', 'prov_mac_address', 'cpus', 'memory_mb', + 'local_gb', 'pm_address', 'pm_user', 'pm_password', + 'terminal_port') def get_nodes_data(): pass -class EditNodes(workflows.Step): +class EditNodes(CreateNodes): action_class = NodeEditAction depends_on = ('rack_id',) contributes = ('node_macs',) - help_text = _("Editing nodes via textbox is not presently supported.") + # help_text = _("Editing nodes via textbox is not presently supported.") class CreateRack(workflows.Workflow): @@ -144,17 +170,28 @@ class CreateRack(workflows.Workflow): def handle(self, request, data): try: + if data['node_name'] is not None: + node = api + node = api.tuskar.Node.manager().create(data['node_name'], + data['cpus'], data['memory_mb'], + data['local_gb'], data['prov_mac_address'], + data['pm_address'], data['pm_user'], + data['pm_password'], data['terminal_port']) + + if node: + node_id = node.id + else: + node_id = None + + # Then, register the Rack, including the node if it exists rack = api.tuskar.Rack.create(request, data['name'], data['resource_class_id'], data['location'], - data['subnet']) - - if data['node_macs'] is not None: - nodes = data['node_macs'].splitlines(False) - api.tuskar.Rack.register_nodes(rack, nodes) + data['subnet'], + [{'id': node_id}]) return True - except: + except Exception as ex: exceptions.handle(request, _("Unable to create rack.")) diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index f0a2f142..821304cd 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -355,4 +355,10 @@ SECURITY_GROUP_RULES = { # FIXME: this will eventually be unneeded, as it will be retrieved from Keystone #TUSKAR_ENDPOINT_URL = "http://127.0.0.1:6385" - +#NOVA_BAREMETAL_CREDS = { +# 'user': 'admin', +# 'password': 'admin_password_here', +# 'tenant': 'admin', +# 'auth_url': 'http://localhost:5001/v2.0/', +# 'bypass_url': 'http://localhost:9774/v2/692567cd99f84f5d8f26ec23ff0ba460' +#} |