Merge branch 'master' into feature/ceilometerDashboard
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7d279c5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+## Getting Started with XOS and CORD
+
+For a general introduction to XOS and how it is used in CORD, see
+http://guide.xosproject.org. The "Developer Guide" at that URL is
+especially helpful, although it is sync'ed with the previous
+release (currently Burwell), which likely lags what's in master.
+Additional design notes, presentations, and other collateral are
+also available at http://xosproject.org.
+
+The quickest way to get started is to look at the collection of
+"stock" configurations in *xos/configurations*. The *cord*
+configuration in that directory corresponds to our current
+CORD development environment, and the README you'll find there
+will help you get started.
diff --git a/containers/postgresql/Makefile b/containers/postgresql/Makefile
index 38f159c..c50923e 100644
--- a/containers/postgresql/Makefile
+++ b/containers/postgresql/Makefile
@@ -1,5 +1,5 @@
-IMAGE_NAME:=xosproject/xos-postgress
-CONTAINER_NAME:=xos-db-postgress
+IMAGE_NAME:=xosproject/xos-postgres
+CONTAINER_NAME:=xos-db-postgres
NO_DOCKER_CACHE?=false
.PHONY: build
diff --git a/containers/xos-compose.yml b/containers/xos-compose.yml
index 27a3b5a..464b560 100644
--- a/containers/xos-compose.yml
+++ b/containers/xos-compose.yml
@@ -1,5 +1,5 @@
xos_db:
- image: xosproject/xos-postgress
+ image: xosproject/xos-postgres
expose:
- "5432"
diff --git a/containers/xos/Dockerfile b/containers/xos/Dockerfile
index 37bd55b..38e08a1 100644
--- a/containers/xos/Dockerfile
+++ b/containers/xos/Dockerfile
@@ -47,7 +47,7 @@
django-geoposition \
django-ipware \
django_rest_swagger \
- django-suit \
+ django-suit==0.3a1 \
django-timezones \
djangorestframework==2.4.4 \
dnslib \
diff --git a/containers/xos/Dockerfile.templ b/containers/xos/Dockerfile.templ
index e669692..5071f68 100644
--- a/containers/xos/Dockerfile.templ
+++ b/containers/xos/Dockerfile.templ
@@ -47,7 +47,7 @@
django-geoposition \
django-ipware \
django_rest_swagger \
- django-suit \
+ django-suit==0.3a1 \
django-timezones \
djangorestframework==2.4.4 \
dnslib \
diff --git a/xos/configurations/common/Makefile.devstack b/xos/configurations/common/Makefile.devstack
new file mode 100644
index 0000000..2f31696
--- /dev/null
+++ b/xos/configurations/common/Makefile.devstack
@@ -0,0 +1,34 @@
+# This shouldn't be hardcoded
+DEVSTACK_ROOT:=~/devstack
+
+all: prereqs admin-openrc flat_name nodes_yaml public_key private_key ceilometer_url node_key
+
+prereqs:
+ make -f Makefile.prereqs
+
+admin-openrc:
+ bash ./devstack-creds.sh $(DEVSTACK_ROOT) > admin-openrc.sh
+ touch controller_settings
+
+flat_name:
+ echo private|tr -d '\n' > flat_net_name
+ bash -c "source admin-openrc.sh; openstack network set --share private"
+
+nodes_yaml:
+ bash ./make-cloudlab-nodes-yaml.sh
+
+ceilometer_url:
+ echo http://`hostname -i`/xosmetering/ > ceilometer_url
+
+public_key: ~/.ssh/id_rsa.pub
+ cp ~/.ssh/id_rsa.pub .
+
+private_key: ~/.ssh/id_rsa
+ cp ~/.ssh/id_rsa .
+
+~/.ssh/id_rsa.pub:
+ cat /dev/zero | ssh-keygen -q -N ""
+
+node_key:
+ sudo cat ~/.ssh/id_rsa > node_key
+ sudo cat ~/.ssh/id_rsa.pub > node_key.pub
diff --git a/xos/configurations/common/devstack-creds.sh b/xos/configurations/common/devstack-creds.sh
new file mode 100644
index 0000000..b90e6ec
--- /dev/null
+++ b/xos/configurations/common/devstack-creds.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+DEVSTACK_ROOT=$1
+
+source $DEVSTACK_ROOT/openrc admin admin
+echo export OS_TENANT_NAME=$OS_TENANT_NAME
+echo export OS_USERNAME=$OS_USERNAME
+echo export OS_PASSWORD=$OS_PASSWORD
+echo export OS_AUTH_URL=$OS_AUTH_URL
diff --git a/xos/configurations/common/devstack.yaml b/xos/configurations/common/devstack.yaml
new file mode 100644
index 0000000..cd17b1c
--- /dev/null
+++ b/xos/configurations/common/devstack.yaml
@@ -0,0 +1,87 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+# Note:
+# assumes the following have been created and filled with appropriate data:
+# /root/setup/admin_openrc
+# /root/setup/flat_net_name
+# /root/setup/padmin_public_key
+
+description: >
+ * Adds OpenCloud Sites, Deployments, and Controllers.
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ cirros-0.3.4-x86_64-uec:
+ type: tosca.nodes.Image
+ properties:
+ disk_format: RAW
+ container_format: BARE
+
+ MyDeployment:
+ type: tosca.nodes.Deployment
+ properties:
+ flavors: m1.large, m1.medium, m1.small
+ requirements:
+ - image:
+ node: cirros-0.3.4-x86_64-uec
+ relationship: tosca.relationships.SupportsImage
+
+ CloudLab:
+ type: tosca.nodes.Controller
+ requirements:
+ - deployment:
+ node: MyDeployment
+ relationship: tosca.relationships.ControllerDeployment
+ properties:
+ backend_type: OpenStack
+ version: Kilo
+ auth_url: { get_script_env: [ SELF, adminrc, OS_AUTH_URL, LOCAL_FILE] }
+ admin_user: { get_script_env: [ SELF, adminrc, OS_USERNAME, LOCAL_FILE] }
+ admin_password: { get_script_env: [ SELF, adminrc, OS_PASSWORD, LOCAL_FILE] }
+ admin_tenant: { get_script_env: [ SELF, adminrc, OS_TENANT_NAME, LOCAL_FILE] }
+ rabbit_user: { get_script_env: [ SELF, controller_settings, RABBIT_USER, LOCAL_FILE] }
+ rabbit_password: { get_script_env: [ SELF, controller_settings, RABBIT_PASS, LOCAL_FILE] }
+ rabbit_host: { get_script_env: [ SELF, controller_settings, CONTROLLER_IP, LOCAL_FILE] }
+ domain: Default
+ artifacts:
+ adminrc: /root/setup/admin-openrc.sh
+ controller_settings: /root/setup/controller_settings
+
+ mysite:
+ type: tosca.nodes.Site
+ properties:
+ display_name: MySite
+ site_url: http://opencloud.us/
+ requirements:
+ - deployment:
+ node: MyDeployment
+ relationship: tosca.relationships.SiteDeployment
+ requirements:
+ - controller:
+ node: CloudLab
+ relationship: tosca.relationships.UsesController
+
+ Public shared IPv4:
+ type: tosca.nodes.NetworkTemplate
+ properties:
+ visibility: private
+ translation: NAT
+ shared_network_name: private
+
+ padmin@vicci.org:
+ type: tosca.nodes.User
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ properties:
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE ] }
+ is_admin: true
+ is_active: true
+ firstname: XOS
+ lastname: admin
+ artifacts:
+ pubkey: /root/setup/padmin_public_key
diff --git a/xos/configurations/common/make-cloudlab-nodes-yaml.sh b/xos/configurations/common/make-cloudlab-nodes-yaml.sh
index e68adf1..f2555a4 100644
--- a/xos/configurations/common/make-cloudlab-nodes-yaml.sh
+++ b/xos/configurations/common/make-cloudlab-nodes-yaml.sh
@@ -19,7 +19,7 @@
type: tosca.nodes.Site
EOF
-NODES=$( sudo bash -c "source /root/setup/admin-openrc.sh ; nova hypervisor-list" |grep cloudlab|awk '{print $4}' )
+NODES=$( bash -c "source ./admin-openrc.sh ; nova hypervisor-list" |grep enabled|awk '{print $4}' )
I=0
for NODE in $NODES; do
echo $NODE
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 02137ec..a35454b 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -108,6 +108,21 @@
kind: onos
view_url: /admin/onos/onosservice/$id$/
public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ rest_onos/v1/network/configuration/: >
+ {
+ "devices" : {
+ "of:0000000000000001" : {
+ "accessDevice" : {
+ "uplink" : "2",
+ "vlan" : "222",
+ "defaultVlan" : "1"
+ },
+ "basic" : {
+ "driver" : "default"
+ }
+ }
+ }
+ }
artifacts:
pubkey: /opt/xos/observers/onos/onos_key.pub
@@ -123,21 +138,21 @@
relationship: tosca.relationships.UsedByService
properties:
dependencies: org.onosproject.openflow-base, org.onosproject.olt
- config_network-cfg.json: >
- {
- "devices" : {
- "of:0000000000000001" : {
- "accessDevice" : {
- "uplink" : "2",
- "vlan" : "222",
- "defaultVlan" : "1"
- },
- "basic" : {
- "driver" : "default"
- }
- }
- }
- }
+# config_network-cfg.json: >
+# {
+# "devices" : {
+# "of:0000000000000001" : {
+# "accessDevice" : {
+# "uplink" : "2",
+# "vlan" : "222",
+# "defaultVlan" : "1"
+# },
+# "basic" : {
+# "driver" : "default"
+# }
+# }
+# }
+# }
# Network templates
Private:
diff --git a/xos/configurations/devel/Makefile b/xos/configurations/devel/Makefile
index 3ce188b..24b7be7 100644
--- a/xos/configurations/devel/Makefile
+++ b/xos/configurations/devel/Makefile
@@ -2,16 +2,23 @@
RUNNING_CONTAINER:=$(shell sudo docker ps|grep "xos"|awk '{print $$NF}')
LAST_CONTAINER=$(shell sudo docker ps -l -q)
-test: common_cloudlab
+cloudlab: common_cloudlab xos
+
+devstack: common_devstack xos
+
+xos:
echo "# Autogenerated -- do not edit" > Dockerfile
cat ../common/Dockerfile.common Dockerfile.devel >> Dockerfile
- cd ../../..; sudo docker build -t xos -f xos/configurations/devel/Dockerfile .
- sudo docker run -d --add-host="ctl:$(MYIP)" -p 9999:8000 xos
+ cd ../../..; sudo docker build -t xosproject/xos-devel -f xos/configurations/devel/Dockerfile .
+ sudo docker run -d --add-host="ctl:$(MYIP)" -p 9999:8000 xosproject/xos-devel
bash ../common/wait_for_xos.sh
common_cloudlab:
make -C ../common -f Makefile.cloudlab
+common_devstack:
+ make -C ../common -f Makefile.devstack
+
stop:
sudo docker stop $(RUNNING_CONTAINER)
diff --git a/xos/configurations/devel/README b/xos/configurations/devel/README
deleted file mode 100644
index b69f24f..0000000
--- a/xos/configurations/devel/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This configuration launches an XOS container on Cloudlab that runs the XOS develserver. The container is left running
-in the backgorund.
-
diff --git a/xos/configurations/devel/README.md b/xos/configurations/devel/README.md
new file mode 100644
index 0000000..47cc6e7
--- /dev/null
+++ b/xos/configurations/devel/README.md
@@ -0,0 +1,58 @@
+# XOS development environment
+
+This configuration can be used to do basic end-to-end development of XOS. It launches
+an XOS container, runs the XOS develserver, and configures XOS to talk to an OpenStack
+backend.
+
+## How to run it
+
+The configuration can be either run on [CloudLab](http://cloudlab.us) (controlling
+an OpenStack backend set up by a CloudLab profile) or used with a basic
+[DevStack](http://docs.openstack.org/developer/devstack/) configuration.
+
+### CloudLab
+
+To get started on CloudLab:
+* Create an experiment using the *OpenStack* profile. Choose *Kilo* and
+disable security groups.
+* Wait until you get an email from CloudLab with title "OpenStack Instance Finished Setting Up".
+* Login to the *ctl* node of your experiment and run:
+```
+ctl:~$ git clone https://github.com/open-cloud/xos.git
+ctl:~$ cd xos/xos/configurations/devel/
+ctl:~/xos/xos/configurations/devel$ make cloudlab
+```
+
+### DevStack
+
+The following instructions can be used to install DevStack and XOS together
+on a single node. This setup has been run successfully in a VirtualBox VM
+with 2 CPUs and 4096 GB RAM.
+
+First, if you happen to be installing DevStack on a CloudLab node, you can
+configure about 1TB of unallocated disk space for DevStack as follows:
+```
+~$ sudo mkdir -p /opt/stack
+~$ sudo /usr/testbed/bin/mkextrafs /opt/stack
+```
+
+To install DevStack and XOS:
+
+```
+~$ git clone https://github.com/open-cloud/xos.git
+~$ git clone https://git.openstack.org/openstack-dev/devstack
+~$ cd devstack
+~/devstack$ cp ../xos/xos/configurations/devstack/local.conf .
+~/devstack$ ./stack.sh
+~/devstack$ cd ../xos/xos/configurations/devel/
+~/xos/xos/configurations/devel$ make devstack
+```
+
+Note that there are some issues with the networking setup in this configuration;
+you will be able to create VMs but they are not accessible on the network. However it is
+possible to log into a VM by first entering the appropriate network namespace.
+
+## What you get
+
+XOS will be set up with a single Deployment and Site. It should be in a state where
+you can create slices and associate instances with them.
diff --git a/xos/configurations/devstack/local.conf b/xos/configurations/devstack/local.conf
new file mode 100644
index 0000000..946d16e
--- /dev/null
+++ b/xos/configurations/devstack/local.conf
@@ -0,0 +1,17 @@
+# A single node devstack configuration for use with XOS
+[[local|localrc]]
+
+DOWNLOAD_DEFAULT_IMAGES=false
+IMAGE_URLS="http://www.planet-lab.org/cord/trusty-server-multi-nic.img"
+
+disable_service n-net
+enable_service q-svc
+enable_service q-agt
+enable_service q-dhcp
+enable_service q-l3
+enable_service q-meta
+# Optional, to enable tempest configuration as part of devstack
+enable_service tempest
+
+## Neutron options
+Q_USE_SECGROUP=False
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index ba54a33..2b450e0 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -83,6 +83,13 @@
service_ids = [sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
return cls.objects.filter(id__in=service_ids)
+ @property
+ def serviceattribute_dict(self):
+ attrs = {}
+ for attr in self.serviceattributes.all():
+ attrs[attr.name] = attr.value
+ return attrs
+
def __unicode__(self): return u'%s' % (self.name)
def can_update(self, user):
@@ -161,8 +168,42 @@
# print "add instance", s
+ def get_vtn_nets(self):
+ nets=[]
+ for slice in self.slices.all():
+ for ns in slice.networkslices.all():
+ if not ns.network:
+ continue
+ for cn in ns.network.controllernetworks.all():
+ if cn.net_id:
+ net = {"name": ns.network.name, "net_id": cn.net_id}
+ nets.append(net)
+ return nets
+
+ def get_vtn_dependencies_nets(self):
+ provider_nets = []
+ for tenant in self.subscribed_tenants.all():
+ if tenant.provider_service:
+ for net in tenant.provider_service.get_vtn_nets():
+ if not net in provider_nets:
+ provider_nets.append(net)
+ return provider_nets
+
+ def get_vtn_dependencies_ids(self):
+ return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
+
+ def get_vtn_dependencies_names(self):
+ return [x["name"]+"_"+x["net_id"] for x in self.get_vtn_dependencies_nets()]
+
+ def get_vtn_ids(self):
+ return [x["net_id"] for x in self.get_vtn_nets()]
+
+ def get_vtn_names(self):
+ return [x["name"]+"_"+x["net_id"] for x in self.get_vtn_nets()]
+
+
class ServiceAttribute(PlCoreBase):
- name = models.SlugField(help_text="Attribute Name", max_length=128)
+ name = models.CharField(help_text="Attribute Name", max_length=128)
value = StrippedCharField(help_text="Attribute Value", max_length=1024)
service = models.ForeignKey(Service, related_name='serviceattributes', help_text="The Service this attribute is associated with")
@@ -310,6 +351,13 @@
def get_deleted_tenant_objects(cls):
return cls.deleted_objects.filter(kind = cls.KIND)
+ @property
+ def tenantattribute_dict(self):
+ attrs = {}
+ for attr in self.tenantattributes.all():
+ attrs[attr.name] = attr.value
+ return attrs
+
# helper function to be used in subclasses that want to ensure service_specific_id is unique
def validate_unique_service_specific_id(self):
if self.pk is None:
@@ -361,6 +409,7 @@
def pick(self):
from core.models import Node
nodes = list(Node.objects.all())
+
# TODO: logic to filter nodes by which nodes are up, and which
# nodes the slice can instantiate on.
nodes = sorted(nodes, key=lambda node: node.instances.all().count())
diff --git a/xos/core/xoslib/methods/ceilometerview.py b/xos/core/xoslib/methods/ceilometerview.py
index a416110..d02e716 100644
--- a/xos/core/xoslib/methods/ceilometerview.py
+++ b/xos/core/xoslib/methods/ceilometerview.py
@@ -1,4 +1,3 @@
-import logging
import requests
from six.moves import urllib
import urllib2
@@ -18,9 +17,9 @@
from django.utils import timezone
from syndicate_storage.models import Volume
from django.core.exceptions import PermissionDenied
+from util.logger import observer_logger as logger
# This REST API endpoint provides information that the ceilometer view needs to display
-LOG = logging.getLogger(__name__)
def getTenantCeilometerProxyURL(user):
monitoring_channel = None
@@ -36,11 +35,12 @@
response = urllib2.urlopen(monitoring_channel.ceilometer_url,timeout=1)
break
except urllib2.HTTPError, e:
- LOG.info('SRIKANTH: HTTP error %(reason)s' % {'reason':e.reason})
+ logger.info('SRIKANTH: HTTP error %(reason)s' % {'reason':e.reason})
break
except urllib2.URLError, e:
- LOG.info('SRIKANTH: URL error %(reason)s' % {'reason':e.reason})
+ logger.info('SRIKANTH: URL error %(reason)s' % {'reason':e.reason})
pass
+ logger.info("SRIKANTH: Ceilometer proxy URL for user %(user)s is %(url)s" % {'user':user.username,'url':monitoring_channel.ceilometer_url})
return monitoring_channel.ceilometer_url
def getTenantControllerTenantMap(user):
@@ -48,7 +48,12 @@
for slice in Slice.objects.filter(creator=user):
for cs in slice.controllerslices.all():
if cs.tenant_id:
- tenantmap[cs.tenant_id] = cs.slice.name
+ tenantmap[cs.tenant_id] = {"slice": cs.slice.name}
+ if cs.slice.service:
+ tenantmap[cs.tenant_id]["service"] = slice.service.name
+ else:
+ logger.warn("SRIKANTH: Slice %(slice)s is not associated with any service" % {'slice':cs.slice.name})
+ tenantmap[cs.tenant_id]["service"] = "Other"
return tenantmap
def build_url(path, q, params=None):
@@ -171,7 +176,6 @@
try:
query=[]
self._ceilometer_meter_list = meter_list(request, self.ceilometer_url, query)
- #LOG.info('SRIKANTH: meters=%(meters)s'%{'meters':[m.project_id for m in self._ceilometer_meter_list]})
except requests.exceptions.RequestException as e:
self._ceilometer_meter_list = []
raise e
@@ -354,9 +358,9 @@
meters = []
for meter_name in meter_names:
- meter = self._get_meter(meter_name)
- if meter:
- meters.append(meter)
+ meter_candidates = self._get_meter(meter_name)
+ if meter_candidates:
+ meters.extend(meter_candidates)
return meters
def _get_meter(self, meter_name):
@@ -368,8 +372,8 @@
:Parameters:
- `meter_name`: A meter name we want to fetch.
"""
- meter = self._cached_meters.get(meter_name, None)
- if not meter:
+ meter_candidates = self._cached_meters.get(meter_name, None)
+ if not meter_candidates:
meter_candidates = [m for m in self._ceilometer_meter_list
if m["name"] == meter_name]
@@ -378,20 +382,25 @@
if meter_info:
label = meter_info["label"]
description = meter_info["description"]
+ meter_type = meter_info["type"]
else:
label = ""
description = ""
- meter = meter_candidates[0]
- meter["label"] = label
- meter["description"] = description
- if meter["project_id"] in self.tenant_map.keys():
- meter["project_name"] = self.tenant_map[meter["project_id"]]
- else:
- meter["project_name"] = meter["project_id"]
+ meter_type = "Other"
+ for meter in meter_candidates:
+ meter["label"] = label
+ meter["description"] = description
+ meter["type"] = meter_type
+ if meter["project_id"] in self.tenant_map.keys():
+ meter["slice"] = self.tenant_map[meter["project_id"]]["slice"]
+ meter["service"] = self.tenant_map[meter["project_id"]]["service"]
+ else:
+ meter["slice"] = meter["project_id"]
+ meter["service"] = "Other"
- self._cached_meters[meter_name] = meter
+ self._cached_meters[meter_name] = meter_candidates
- return meter
+ return meter_candidates
def _get_nova_meters_info(self):
"""Returns additional info for each meter.
@@ -405,40 +414,49 @@
# some day it will be supported all.
meters_info = datastructures.SortedDict([
("instance", {
+ 'type': _("Nova"),
'label': '',
'description': _("Existence of instance"),
}),
("instance:<type>", {
+ 'type': _("Nova"),
'label': '',
'description': _("Existence of instance <type> "
"(openstack types)"),
}),
("memory", {
+ 'type': _("Nova"),
'label': '',
'description': _("Volume of RAM"),
}),
("memory.usage", {
+ 'type': _("Nova"),
'label': '',
'description': _("Volume of RAM used"),
}),
("cpu", {
+ 'type': _("Nova"),
'label': '',
'description': _("CPU time used"),
}),
("cpu_util", {
+ 'type': _("Nova"),
'label': '',
'description': _("Average CPU utilization"),
}),
("vcpus", {
+ 'type': _("Nova"),
'label': '',
'description': _("Number of VCPUs"),
}),
("network.incoming.bytes.rate", {
+ 'type': _("Nova"),
'label': '',
'description': _("Average rate per sec of incoming "
"bytes on a VM network interface"),
}),
("network.outgoing.bytes.rate", {
+ 'type': _("Nova"),
'label': '',
'description': _("Average rate per sec of outgoing "
"bytes on a VM network interface"),
@@ -471,18 +489,22 @@
# some day it will be supported all.
return datastructures.SortedDict([
('network', {
+ 'type': _("Neutron"),
'label': '',
'description': _("Existence of network"),
}),
('subnet', {
+ 'type': _("Neutron"),
'label': '',
'description': _("Existence of subnet"),
}),
('port', {
+ 'type': _("Neutron"),
'label': '',
'description': _("Existence of port"),
}),
('ip.floating', {
+ 'type': _("Neutron"),
'label': '',
'description': _("Existence of floating ip"),
}),
@@ -500,30 +522,37 @@
# some day it will be supported all.
return datastructures.SortedDict([
('image', {
+ 'type': _("Glance"),
'label': '',
'description': _("Image existence check"),
}),
('image.size', {
+ 'type': _("Glance"),
'label': '',
'description': _("Uploaded image size"),
}),
('image.update', {
+ 'type': _("Glance"),
'label': '',
'description': _("Number of image updates"),
}),
('image.upload', {
+ 'type': _("Glance"),
'label': '',
'description': _("Number of image uploads"),
}),
('image.delete', {
+ 'type': _("Glance"),
'label': '',
'description': _("Number of image deletions"),
}),
('image.download', {
+ 'type': _("Glance"),
'label': '',
'description': _("Image is downloaded"),
}),
('image.serve', {
+ 'type': _("Glance"),
'label': '',
'description': _("Image is served out"),
}),
@@ -541,10 +570,12 @@
# some day it will be supported all.
return datastructures.SortedDict([
('volume', {
+ 'type': _("Cinder"),
'label': '',
'description': _("Existence of volume"),
}),
('volume.size', {
+ 'type': _("Cinder"),
'label': '',
'description': _("Size of volume"),
}),
@@ -562,26 +593,32 @@
# some day it will be supported all.
return datastructures.SortedDict([
('storage.objects', {
+ 'type': _("Swift"),
'label': '',
'description': _("Number of objects"),
}),
('storage.objects.size', {
+ 'type': _("Swift"),
'label': '',
'description': _("Total size of stored objects"),
}),
('storage.objects.containers', {
+ 'type': _("Swift"),
'label': '',
'description': _("Number of containers"),
}),
('storage.objects.incoming.bytes', {
+ 'type': _("Swift"),
'label': '',
'description': _("Number of incoming bytes"),
}),
('storage.objects.outgoing.bytes', {
+ 'type': _("Swift"),
'label': '',
'description': _("Number of outgoing bytes"),
}),
('storage.api.request', {
+ 'type': _("Swift"),
'label': '',
'description': _("Number of API requests against swift"),
}),
@@ -599,10 +636,12 @@
# some day it will be supported all.
return datastructures.SortedDict([
('energy', {
+ 'type': _("Kwapi"),
'label': '',
'description': _("Amount of energy"),
}),
('power', {
+ 'type': _("Kwapi"),
'label': '',
'description': _("Power consumption"),
}),
@@ -620,50 +659,62 @@
# some day it will be supported all.
return datastructures.SortedDict([
('hardware.ipmi.node.power', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System Current Power"),
}),
('hardware.ipmi.fan', {
+ 'type': _("IPMI"),
'label': '',
'description': _("Fan RPM"),
}),
('hardware.ipmi.temperature', {
+ 'type': _("IPMI"),
'label': '',
'description': _("Sensor Temperature Reading"),
}),
('hardware.ipmi.current', {
+ 'type': _("IPMI"),
'label': '',
'description': _("Sensor Current Reading"),
}),
('hardware.ipmi.voltage', {
+ 'type': _("IPMI"),
'label': '',
'description': _("Sensor Voltage Reading"),
}),
('hardware.ipmi.node.inlet_temperature', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System Inlet Temperature Reading"),
}),
('hardware.ipmi.node.outlet_temperature', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System Outlet Temperature Reading"),
}),
('hardware.ipmi.node.airflow', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System Airflow Reading"),
}),
('hardware.ipmi.node.cups', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System CUPS Reading"),
}),
('hardware.ipmi.node.cpu_util', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System CPU Utility Reading"),
}),
('hardware.ipmi.node.mem_util', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System Memory Utility Reading"),
}),
('hardware.ipmi.node.io_util', {
+ 'type': _("IPMI"),
'label': '',
'description': _("System IO Utility Reading"),
}),
@@ -681,34 +732,42 @@
# some day it will be supported all.
return datastructures.SortedDict([
('vcpe', {
+ 'type': _("VCPE"),
'label': '',
'description': _("Existence of vcpe instance"),
}),
('vcpe.dns.cache.size', {
+ 'type': _("VCPE"),
'label': '',
'description': _("Number of entries in DNS cache"),
}),
('vcpe.dns.total_instered_entries', {
+ 'type': _("VCPE"),
'label': '',
'description': _("Total number of inserted entries into the cache"),
}),
('vcpe.dns.replaced_unexpired_entries', {
+ 'type': _("VCPE"),
'label': '',
'description': _("Unexpired entries that were thrown out of cache"),
}),
('vcpe.dns.queries_answered_locally', {
+ 'type': _("VCPE"),
'label': '',
'description': _("Number of cache hits"),
}),
('vcpe.dns.queries_forwarded', {
+ 'type': _("VCPE"),
'label': '',
'description': _("Number of cache misses"),
}),
('vcpe.dns.server.queries_sent', {
+ 'type': _("VCPE"),
'label': '',
'description': _("For each upstream server, the number of queries sent"),
}),
('vcpe.dns.server.queries_failed', {
+ 'type': _("VCPE"),
'label': '',
'description': _("For each upstream server, the number of queries failed"),
}),
@@ -726,50 +785,62 @@
# some day it will be supported all.
return datastructures.SortedDict([
('switch', {
+ 'type': _("SDN"),
'label': '',
'description': _("Existence of switch"),
}),
('switch.port', {
+ 'type': _("SDN"),
'label': '',
'description': _("Existence of port"),
}),
('switch.port.receive.packets', {
+ 'type': _("SDN"),
'label': '',
'description': _("Packets received on port"),
}),
('switch.port.transmit.packets', {
+ 'type': _("SDN"),
'label': '',
'description': _("Packets transmitted on port"),
}),
('switch.port.receive.drops', {
+ 'type': _("SDN"),
'label': '',
'description': _("Drops received on port"),
}),
('switch.port.transmit.drops', {
+ 'type': _("SDN"),
'label': '',
'description': _("Drops transmitted on port"),
}),
('switch.port.receive.errors', {
+ 'type': _("SDN"),
'label': '',
'description': _("Errors received on port"),
}),
('switch.port.transmit.errors', {
+ 'type': _("SDN"),
'label': '',
'description': _("Errors transmitted on port"),
}),
('switch.flow', {
+ 'type': _("SDN"),
'label': '',
'description': _("Duration of flow"),
}),
('switch.flow.packets', {
+ 'type': _("SDN"),
'label': '',
'description': _("Packets received"),
}),
('switch.table', {
+ 'type': _("SDN"),
'label': '',
'description': _("Existence of table"),
}),
('switch.table.active.entries', {
+ 'type': _("SDN"),
'label': '',
'description': _("Active entries in table"),
}),
@@ -865,7 +936,10 @@
_('VCPE'): meters.list_vcpe(),
_('SDN'): meters.list_sdn(),
}
- return Response(meters._cached_meters.values())
+ meters = []
+ for service,smeters in services.iteritems():
+ meters.extend(smeters)
+ return Response(meters)
class MeterStatisticsList(APIView):
method_kind = "list"
@@ -890,6 +964,39 @@
except Exception as e:
raise e
+ additional_query = []
+ if date_from:
+ additional_query.append({'field': 'timestamp',
+ 'op': 'ge',
+ 'value': date_from})
+ if date_to:
+ additional_query.append({'field': 'timestamp',
+ 'op': 'le',
+ 'value': date_to})
+
+ meter_name = request.QUERY_PARAMS.get('meter', None)
+ tenant_id = request.QUERY_PARAMS.get('tenant', None)
+ resource_id = request.QUERY_PARAMS.get('resource', None)
+
+ if meter_name:
+ #Statistics query for one meter
+ query = []
+ if tenant_id:
+ query.extend(make_query(tenant_id=tenant_id))
+ if resource_id:
+ query.extend(make_query(resource_id=resource_id))
+ if additional_query:
+ query = query + additional_query
+ statistics = statistic_list(request, meter_name,
+ ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
+ statistic = statistics[-1]
+ row = {"name": 'none',
+ "meter": meter_name,
+ "time": statistic["period_end"],
+ "value": statistic["avg"]}
+ return Response(row)
+
+ #Statistics query for all meter
meters = Meters(request, ceilometer_url=tenant_ceilometer_url, tenant_map=tenant_map)
services = {
_('Nova'): meters.list_nova(),
@@ -900,17 +1007,21 @@
report_rows = []
for service,meters in services.items():
for meter in meters:
- query = make_query(tenant_id=meter["project_id"])
+ query = make_query(tenant_id=meter["project_id"],resource_id=meter["resource_id"])
+ if additional_query:
+ query = query + additional_query
statistics = statistic_list(request, meter["name"],
ceilometer_url=tenant_ceilometer_url, query=query, period=3600*24)
if not statistics:
continue
- statistic = statistics[0]
+ statistic = statistics[-1]
row = {"name": 'none',
- "project": meter["project_name"],
+ "slice": meter["slice"],
+ "service": meter["service"],
+ "resource_id": meter["resource_id"],
"meter": meter["name"],
"description": meter["description"],
- "service": service,
+ "type": service,
"time": statistic["period_end"],
"value": statistic["avg"],
"unit": meter["unit"]}
diff --git a/xos/core/xoslib/methods/cordsubscriber.py b/xos/core/xoslib/methods/cordsubscriber.py
index 297ac4a..3892014 100644
--- a/xos/core/xoslib/methods/cordsubscriber.py
+++ b/xos/core/xoslib/methods/cordsubscriber.py
@@ -11,7 +11,7 @@
from django.conf.urls import patterns, url
from cord.models import VOLTTenant, VBNGTenant, CordSubscriberRoot
from core.xoslib.objects.cordsubscriber import CordSubscriber
-from plus import PlusSerializerMixin
+from plus import PlusSerializerMixin, XOSViewSet
from django.shortcuts import get_object_or_404
from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
from xos.exceptions import *
@@ -158,30 +158,6 @@
subscriber.save()
return Response(serialize_user(subscriber,user))
-# this may be moved into plus.py...
-
-class XOSViewSet(viewsets.ModelViewSet):
- @classmethod
- def detail_url(self, pattern, viewdict, name):
- return url(r'^' + self.method_name + r'/(?P<pk>[a-zA-Z0-9\-]+)/' + pattern,
- self.as_view(viewdict),
- name=self.base_name+"_"+name)
-
- @classmethod
- def list_url(self, pattern, viewdict, name):
- return url(r'^' + self.method_name + r'/' + pattern,
- self.as_view(viewdict),
- name=self.base_name+"_"+name)
-
- @classmethod
- def get_urlpatterns(self):
- patterns = []
-
- patterns.append(url(r'^' + self.method_name + '/$', self.as_view({'get': 'list'}), name=self.base_name+'_list'))
- patterns.append(url(r'^' + self.method_name + '/(?P<pk>[a-zA-Z0-9\-]+)/$', self.as_view({'get': 'retrieve', 'put': 'update', 'post': 'update', 'delete': 'destroy', 'patch': 'partial_update'}), name=self.base_name+'_detail'))
-
- return patterns
-
#------------------------------------------------------------------------------
# The "new" API with many more REST endpoints.
# This is for integration with with the subscriber GUI
diff --git a/xos/core/xoslib/methods/plus.py b/xos/core/xoslib/methods/plus.py
index 280c468..294fba8 100644
--- a/xos/core/xoslib/methods/plus.py
+++ b/xos/core/xoslib/methods/plus.py
@@ -3,6 +3,8 @@
from rest_framework.response import Response
from rest_framework import status
from xos.apibase import XOSRetrieveUpdateDestroyAPIView, XOSListCreateAPIView
+from rest_framework import viewsets
+from django.conf.urls import patterns, url
""" PlusSerializerMixin
@@ -26,7 +28,24 @@
def getBackendHtml(self, obj):
return obj.getBackendHtml()
+class XOSViewSet(viewsets.ModelViewSet):
+ @classmethod
+ def detail_url(self, pattern, viewdict, name):
+ return url(r'^' + self.method_name + r'/(?P<pk>[a-zA-Z0-9\-]+)/' + pattern,
+ self.as_view(viewdict),
+ name=self.base_name+"_"+name)
+ @classmethod
+ def list_url(self, pattern, viewdict, name):
+ return url(r'^' + self.method_name + r'/' + pattern,
+ self.as_view(viewdict),
+ name=self.base_name+"_"+name)
+ @classmethod
+ def get_urlpatterns(self):
+ patterns = []
+ patterns.append(url(r'^' + self.method_name + '/$', self.as_view({'get': 'list'}), name=self.base_name+'_list'))
+ patterns.append(url(r'^' + self.method_name + '/(?P<pk>[a-zA-Z0-9\-]+)/$', self.as_view({'get': 'retrieve', 'put': 'update', 'post': 'update', 'delete': 'destroy', 'patch': 'partial_update'}), name=self.base_name+'_detail'))
+ return patterns
diff --git a/xos/core/xoslib/methods/vtn.py b/xos/core/xoslib/methods/vtn.py
new file mode 100644
index 0000000..a912613
--- /dev/null
+++ b/xos/core/xoslib/methods/vtn.py
@@ -0,0 +1,64 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import viewsets
+from rest_framework.decorators import detail_route, list_route
+from rest_framework.views import APIView
+from core.models import *
+from django.forms import widgets
+from django.conf.urls import patterns, url
+from plus import PlusSerializerMixin, XOSViewSet
+from django.shortcuts import get_object_or_404
+from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
+from xos.exceptions import *
+import json
+import subprocess
+
+class VTNViewSet(XOSViewSet):
+ base_name = "vtn"
+ method_name = "rs/vtn"
+ method_kind = "viewset"
+
+ # these are just because ViewSet needs some queryset and model, even if we don't use the
+ # default endpoints
+ queryset = Service.objects.none() # CordSubscriber.get_tenant_objects().select_related().all()
+ model = Service
+
+ @classmethod
+ def get_urlpatterns(self):
+ patterns = []
+ patterns.append( self.list_url("services/$", {"get": "get_services"}, "services") )
+ patterns.append( self.list_url("services_names/$", {"get": "get_services_names"}, "services") )
+ patterns.append( self.list_url("services/(?P<service>[a-zA-Z0-9\-_]+)/$", {"get": "get_service"}, "get_service") )
+
+ return patterns
+
+ def get_services_names(self, request, pk=None):
+ result = {}
+ for service in Service.objects.all():
+ for id in service.get_vtn_names():
+ dependencies = service.get_vtn_dependencies_names()
+ if dependencies:
+ result[id] = dependencies
+ return Response(result)
+
+ def get_services(self, request, pk=None):
+ result = {}
+ for service in Service.objects.all():
+ for id in service.get_vtn_ids():
+ dependencies = service.get_vtn_dependencies_ids()
+ if dependencies:
+ result[id] = dependencies
+ return Response(result)
+
+ def get_service(self, request, pk=None, service=None):
+ for xos_service in Service.objects.all():
+ if service in xos_service.get_vtn_ids():
+ return Response(xos_service.get_vtn_dependencies_ids())
+ raise DoesNotExist()
+
+ def list(self, request):
+ raise Exception("Not Implemented")
+
diff --git a/xos/observers/monitoring_channel/templates/ceilometer_proxy_server.py b/xos/observers/monitoring_channel/templates/ceilometer_proxy_server.py
index 249c965..9329cfa 100644
--- a/xos/observers/monitoring_channel/templates/ceilometer_proxy_server.py
+++ b/xos/observers/monitoring_channel/templates/ceilometer_proxy_server.py
@@ -223,11 +223,15 @@
"q.op": [],
"q.type": [],
"q.value": [],
+ "limit": None,
+ "links": None,
}
query_params = web.input(**keyword_args)
new_query, user_specified_tenants = filter_query_params(query_params)
client = ceilometerclient()
+ limit=query_params.limit
+ links=query_params.links
resources=[]
for (k,v) in config.items('allowed_tenants'):
if user_specified_tenants and (k not in user_specified_tenants):
@@ -237,7 +241,7 @@
query = make_query(tenant_id=k)
final_query.extend(query)
logger.debug('final query=%s',final_query)
- results = client.resources.list(q=final_query, links=1)
+ results = client.resources.list(q=final_query, limit=limit, links=links)
resources.extend(results)
return json.dumps([ob._info for ob in resources])
diff --git a/xos/observers/onos/steps/sync_onosapp.py b/xos/observers/onos/steps/sync_onosapp.py
index 9b32298..97bb8a6 100644
--- a/xos/observers/onos/steps/sync_onosapp.py
+++ b/xos/observers/onos/steps/sync_onosapp.py
@@ -81,20 +81,25 @@
if not os.path.exists(o.files_dir):
os.makedirs(o.files_dir)
- for attr in o.tenantattributes.all():
- if attr.name.startswith("config_"):
- fn = attr.name[7:] # .replace("_json",".json")
+ # Combine the service attributes with the tenant attributes. Tenant
+ # attribute can override service attributes.
+ attrs = o.provider_service.serviceattribute_dict
+ attrs.update(o.tenantattribute_dict)
+
+ for (name, value) in attrs.items():
+ if name.startswith("config_"):
+ fn = name[7:] # .replace("_json",".json")
o.config_fns.append(fn)
- file(os.path.join(o.files_dir, fn),"w").write(attr.value)
- if attr.name.startswith("rest_"):
- fn = attr.name[5:].replace("/","_")
- endpoint = attr.name[5:]
+ file(os.path.join(o.files_dir, fn),"w").write(value)
+ if name.startswith("rest_"):
+ fn = name[5:].replace("/","_")
+ endpoint = name[5:]
# Ansible goes out of it's way to make our life difficult. If
# 'lookup' sees a file that it thinks contains json, then it'll
# insist on parsing and return a json object. We just want
# a string, so prepend a space and then strip the space off
# later.
- file(os.path.join(o.files_dir, fn),"w").write(" " +attr.value)
+ file(os.path.join(o.files_dir, fn),"w").write(" " +value)
o.rest_configs.append( {"endpoint": endpoint, "fn": fn} )
def prepare_record(self, o):
diff --git a/xos/observers/onos/steps/sync_onosapp.yaml b/xos/observers/onos/steps/sync_onosapp.yaml
index 9ee2513..9105a2e 100644
--- a/xos/observers/onos/steps/sync_onosapp.yaml
+++ b/xos/observers/onos/steps/sync_onosapp.yaml
@@ -45,18 +45,6 @@
{% endfor %}
{% endif %}
-{% if rest_configs %}
- - name: Add ONOS configuration values
- uri:
- url: http://localhost:8181/{{ '{{' }} item.endpoint {{ '}}' }} #http://localhost:8181/onos/v1/network/configuration/
- body: "{{ '{{' }} item.body {{ '}}' }}"
- body_format: raw
- method: POST
- user: karaf
- password: karaf
- with_items: "rest_configs"
-{% endif %}
-
# Don't know how to check for this condition, just wait
- name: Wait for ONOS to install the apps
wait_for: timeout=15
@@ -71,3 +59,17 @@
{% for dependency in dependencies %}
- {{ dependency }}
{% endfor %}
+
+{% if rest_configs %}
+# Do this after services have been activated, or it will cause an exception.
+# vOLT will re-read its net config; vbng may not.
+ - name: Add ONOS configuration values
+ uri:
+ url: http://localhost:8181/{{ '{{' }} item.endpoint {{ '}}' }} #http://localhost:8181/onos/v1/network/configuration/
+ body: "{{ '{{' }} item.body {{ '}}' }}"
+ body_format: raw
+ method: POST
+ user: karaf
+ password: karaf
+ with_items: "rest_configs"
+{% endif %}
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index d4e96ce..a806327 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -101,6 +101,10 @@
xos_base_service_caps
properties:
xos_base_service_props
+ rest_onos/v1/network/configuration/:
+ type: string
+ required: false
+
tosca.nodes.ONOSApp:
derived_from: tosca.nodes.Root
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 0c20211..3339fdf 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -102,6 +102,10 @@
type: string
required: false
description: Version number of Service.
+ rest_onos/v1/network/configuration/:
+ type: string
+ required: false
+
tosca.nodes.ONOSApp:
derived_from: tosca.nodes.Root
diff --git a/xos/tosca/resources/onosservice.py b/xos/tosca/resources/onosservice.py
index 1275fab..b742ebb 100644
--- a/xos/tosca/resources/onosservice.py
+++ b/xos/tosca/resources/onosservice.py
@@ -5,6 +5,7 @@
sys.path.append("/opt/tosca")
from translator.toscalib.tosca_template import ToscaTemplate
+from core.models import ServiceAttribute
from services.onos.models import ONOSService
from service import XOSService
@@ -14,3 +15,27 @@
xos_model = ONOSService
copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "versionNumber"]
+ def set_service_attr(self, obj, prop_name, value):
+ value = self.try_intrinsic_function(value)
+ if value:
+ attrs = ServiceAttribute.objects.filter(service=obj, name=prop_name)
+ if attrs:
+ attr = attrs[0]
+ if attr.value != value:
+ self.info("updating attribute %s" % prop_name)
+ attr.value = value
+ attr.save()
+ else:
+ self.info("adding attribute %s" % prop_name)
+ ta = ServiceAttribute(service=obj, name=prop_name, value=value)
+ ta.save()
+
+ def postprocess(self, obj):
+ props = self.nodetemplate.get_properties()
+ for (k,d) in props.items():
+ v = d.value
+ if k.startswith("config_"):
+ self.set_service_attr(obj, k, v)
+ elif k.startswith("rest_"):
+ self.set_service_attr(obj, k, v)
+