Merged xos/master from open-cloud
diff --git a/containers/README.md b/containers/README.md
new file mode 100644
index 0000000..0bdc74b
--- /dev/null
+++ b/containers/README.md
@@ -0,0 +1,77 @@
+# XOS Docker Images
+
+## Introduction
+
+ XOS is comprised of 3 core services:
+
+ * A database backend (postgres)
+ * A webserver front end (django)
+ * A synchronizer daemon that interacts with the openstack backend.
+
+We have created separate dockerfiles for each of these services, making it
+easier to build the services independently and also deploy and run them in
+isolated environments.
+
+#### Database Container
+
+To build and run the database container:
+
+```
+$ cd postgres; make build && make run
+```
+
+#### XOS Container
+
+To build and run the xos webserver container:
+
+```
+$ cd xos; make build && make run
+```
+
+You should now be able to access the login page by visiting
+`http://localhost:80` and log in using the default `paadmin@vicci.org` account
+with password `letmein`. It may be helpful to bootstrap xos with some sample
+data; deployment, controllers, sites, slices, etc. You can get started by
+loading tosca configuration for the opencloud demo dataset:
+
+```
+$ cd xos; make runtosca
+```
+
+Or you can create you own tosca configuraton file and customize the dataset
+however you want. You can all load your own tosca configuration by setting the
+`TOSCA_CONFIG_PATH` environment variable before executing the make command:
+
+```
+$ cd xos; TOSCA_CONFIG_PATH=/path/to/tosca/config.yaml make runtosca
+```
+
+#### Synchronizer Container
+
+The Synchronizer shares many of the same dependencies as the xos container. The
+synchronizer container takes advantage of this by building itself on top of the
+xos image. This means you must build the xos image before building the
+synchronizer image. The XOS and synchronizer containers can run on separate
+hosts, but you must build the xos image on the host that you plan to run the
+synchronizer container. Assuming you have already built the xos container,
+executing the following will build and run the synchronizer container:
+
+```
+$ cd synchronizer; make build && make run
+```
+
+#### Solution Compose File ![](https://img.shields.io/badge/compose-beta-red.svg)
+
+[Docker Compose](https://docs.docker.com/compose/) is a tool for defining and
+running multi-container Docker applications. With Compose, you use a Compose
+file to configure your application’s services. Then, using a single command, you
+create, start, scale, and manage all the services from your configuration.
+
+Included is a compose file in *YAML* format with content defined by the [Docker
+Compose Format](https://docs.docker.com/compose/compose-file/). With the compose
+file a complete XOS solution based on docker containers can be instantiated
+using a single command. To start the instance you can use the command:
+
+```
+$ docker-compose -f xos-compose.yml up -d
+```
diff --git a/containers/postgresql/Makefile b/containers/postgresql/Makefile
index 327f661..38f159c 100644
--- a/containers/postgresql/Makefile
+++ b/containers/postgresql/Makefile
@@ -1,21 +1,25 @@
+IMAGE_NAME:=xosproject/xos-postgress
+CONTAINER_NAME:=xos-db-postgress
+NO_DOCKER_CACHE?=false
+
.PHONY: build
-build: ; docker build --rm -t postgres .
+build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
.PHONY: run
-run: ; docker run -d -p 5432:5432 --name postgres-server postgres
+run: ; docker run -d -p 5432:5432 --name ${CONTAINER_NAME} ${IMAGE_NAME}
.PHONY: stop
-stop: ; docker stop postgres-server
+stop: ; docker stop ${CONTAINER_NAME}
.PHONY: rm
-rm: ; docker rm postgres-server
+rm: ; docker rm ${CONTAINER_NAME}
.PHONE: rmi
-rmi: ; docker rmi postgres
+rmi: ; docker rmi ${IMAGE_NAME}
.PHONY: backup
-backupvol: ; docker run --volumes-from postgres-server -v /backup:/backup postgres tar cvf /backup/backup-postgres.tar /var/lib/postgresql
+backupvol: ; docker run --volumes-from ${CONTAINER_NAME} -v /backup:/backup postgres tar cvf /backup/backup-postgres.tar /var/lib/postgresql
.PHONY: restore
-restorevol: ; docker run --volumes-from postgres-server -v /backup:/backup postgres cd /var/lib/postgresql && tar xvf /backup/backup-postgres.tar
+restorevol: ; docker run --volumes-from ${CONTAINER_NAME} -v /backup:/backup postgres cd /var/lib/postgresql && tar xvf /backup/backup-postgres.tar
diff --git a/containers/synchronizer/Dockerfile b/containers/synchronizer/Dockerfile
index 44b058e..f9d79ae 100644
--- a/containers/synchronizer/Dockerfile
+++ b/containers/synchronizer/Dockerfile
@@ -1,4 +1,4 @@
-FROM xos
+FROM xosproject/xos
# Install custom Ansible
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
diff --git a/containers/synchronizer/Makefile b/containers/synchronizer/Makefile
index 14520d9..8620438 100644
--- a/containers/synchronizer/Makefile
+++ b/containers/synchronizer/Makefile
@@ -1,10 +1,12 @@
-CONTAINER_NAME:=synchronizer-server
+IMAGE_NAME:=xosproject/xos-synchronizer-openstack
+CONTAINER_NAME:=xos-synchronizer
+NO_DOCKER_CACHE?=false
.PHONY: build
-build: ; docker build --rm -t synchronizer .
+build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
.PHONY: run
-run: ; docker run -d --name ${CONTAINER_NAME} synchronizer
+run: ; docker run -d --name ${CONTAINER_NAME} ${IMAGE_NAME}
.PHONY: stop
stop: ; docker stop ${CONTAINER_NAME}
diff --git a/containers/xos-compose.yml b/containers/xos-compose.yml
new file mode 100644
index 0000000..27a3b5a
--- /dev/null
+++ b/containers/xos-compose.yml
@@ -0,0 +1,25 @@
+xos_db:
+ image: xosproject/xos-postgress
+ expose:
+ - "5432"
+
+xos_synchronizer_openstack:
+ image: xosproject/xos-synchronizer-openstack
+ labels:
+ org.xosproject.kind: synchronizer
+ org.xosproject.target: openstack
+
+# FUTURE
+#xos_swarm_synchronizer:
+# image: xosproject/xos-swarm-synchronizer
+# labels:
+# org.xosproject.kind: synchronizer
+# org.xosproject.target: swarm
+
+xos:
+ image: xosproject/xos
+ command: python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure --makemigrations
+ ports:
+ - "8000:8000"
+ links:
+ - xos_db
diff --git a/containers/xos/Dockerfile.templ b/containers/xos/Dockerfile.templ
new file mode 100644
index 0000000..e669692
--- /dev/null
+++ b/containers/xos/Dockerfile.templ
@@ -0,0 +1,88 @@
+FROM ubuntu:14.04.3
+
+# XXX Workaround for docker bug:
+# https://github.com/docker/docker/issues/6345
+# Kernel 3.15 breaks docker, uss the line below as a workaround
+# until there is a fix
+RUN ln -s -f /bin/true /usr/bin/chfn
+# XXX End workaround
+
+# Install.
+RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
+ curl \
+ gcc \
+ geoip-database \
+ git \
+ graphviz \
+ graphviz-dev \
+ libgeoip1 \
+ libxslt1.1 \
+ libxslt1-dev \
+ libyaml-dev \
+ m4 \
+ pkg-config \
+ python-dev \
+ python-httplib2 \
+ python-pip \
+ python-psycopg2 \
+ python-pycurl \
+ python-setuptools \
+ tar \
+ wget \
+##### observer dependencies
+ python-keystoneclient \
+ python-novaclient \
+ python-neutronclient \
+ python-glanceclient \
+ python-ceilometerclient
+
+RUN pip install -U \
+ django==1.7 \
+ django-bitfield \
+ django-crispy-forms \
+ django-encrypted-fields \
+ django_evolution \
+ django-extensions \
+ django-filter \
+ django-geoposition \
+ django-ipware \
+ django_rest_swagger \
+ django-suit \
+ django-timezones \
+ djangorestframework==2.4.4 \
+ dnslib \
+ google_api_python_client \
+ httplib2 \
+ httplib2.ca_certs_locater \
+ lxml \
+ markdown \
+ netaddr \
+ python-dateutil \
+ python_gflags \
+ python-keyczar \
+ pygraphviz \
+ pytz \
+ pyyaml \
+ requests
+
+ADD http://code.jquery.com/jquery-1.9.1.min.js /usr/local/lib/python2.7/dist-packages/suit/static/suit/js/
+
+# Install XOS
+RUN git clone XOS_GIT_REPO -b XOS_GIT_BRANCH /tmp/xos && \
+ mv /tmp/xos/xos /opt/ && \
+ chmod +x /opt/xos/scripts/opencloud && \
+ /opt/xos/scripts/opencloud genkeys
+
+# install Tosca engine
+RUN bash /opt/xos/tosca/install_tosca.sh
+
+EXPOSE 8000
+
+# Set environment variables.
+ENV HOME /root
+
+# Define working directory.
+WORKDIR /root
+
+# Define default command.
+CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure
diff --git a/containers/xos/Makefile b/containers/xos/Makefile
index c71873e..367f0ec 100644
--- a/containers/xos/Makefile
+++ b/containers/xos/Makefile
@@ -1,11 +1,18 @@
CONTAINER_NAME:=xos-server
+IMAGE_NAME:=xosproject/xos
TOSCA_CONFIG_PATH:=/opt/xos/configurations/opencloud/opencloud.yaml
+XOS_GIT_REPO?=git://github.com/open-cloud/xos.git
+XOS_GIT_BRANCH?=master
+NO_DOCKER_CACHE?=false
.PHONY: build
-build: ; docker build --rm -t xos . && ./initdb
+build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
+
+.PHONY: custom
+custom: ; cat Dockerfile.templ | sed -e "s|XOS_GIT_REPO|${XOS_GIT_REPO}|g" -e "s|XOS_GIT_BRANCH|${XOS_GIT_BRANCH}|g" | docker build --no-cache=${NO_DOCKER_CACHE} -
.PHONY: run
-run: ; docker run -d --name ${CONTAINER_NAME} -p 80:8000 xos
+run: ; docker run -d --name ${CONTAINER_NAME} -p 80:8000 ${IMAGE_NAME}
.PHONY: runtosca
runtosca: ; docker exec -it ${CONTAINER_NAME} /usr/bin/python /opt/xos/tosca/run.py padmin@vicci.org ${TOSCA_CONFIG_PATH}
diff --git a/containers/xos/initdb b/containers/xos/initdb
index bd020c5..41e0a9a 100755
--- a/containers/xos/initdb
+++ b/containers/xos/initdb
@@ -1,16 +1,17 @@
#!/bin/bash
-IMAGE_NAME=xos
+IMAGE_NAME=xosproject/xos
+CONTAINER_NAME=xos_build_helper_$$
DB_HOST=$(wget http://ipinfo.io/ip -qO -)
# configure db host
-docker run -it $IMAGE_NAME sed -i '0,/host/{s/host=localhost/host='$DB_HOST'/}' /opt/xos/xos_config
-CONTAINER_ID=$(docker ps -a | grep $IMAGE_NAME | head -1 | awk '{print $1}')
-echo $CONTAINER_ID $IMAGE_NAME
-docker commit $CONTAINER_ID $IMAGE_NAME
+docker run -it --name=$CONTAINER_NAME $IMAGE_NAME sed -i '0,/host/{s/host=localhost/host='$DB_HOST'/}' /opt/xos/xos_config
+docker commit $CONTAINER_NAME $IMAGE_NAME
+docker rm $CONTAINER_NAME
# init db schema
-docker run -it $IMAGE_NAME /opt/xos/scripts/opencloud makemigrations
-CONTAINER_ID=$(docker ps -a | grep $IMAGE_NAME | head -1 | awk '{print $1}')
+docker run -it --name=$CONTAINER_NAME $IMAGE_NAME /opt/xos/scripts/opencloud makemigrations
# run overrides the CMD specifed in the Dockerfile, so we re-set the CMD in the final commit"
-docker commit --change "CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_ID $IMAGE_NAME
+echo docker commit --change="CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_NAME $IMAGE_NAME
+docker commit --change="CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_NAME $IMAGE_NAME
+docker rm $CONTAINER_NAME
diff --git a/xos/configurations/cord/Dockerfile.cord b/xos/configurations/cord/Dockerfile.cord
index f7fdb65..8734eef 100644
--- a/xos/configurations/cord/Dockerfile.cord
+++ b/xos/configurations/cord/Dockerfile.cord
@@ -19,6 +19,7 @@
ADD xos/observers/monitoring_channel/supervisor/monitoring_channel_observer.conf /etc/supervisor/conf.d/
RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/vcpe/vcpe_observer_config
RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/monitoring_channel/monitoring_channel_observer_config
+ADD xos/configurations/cord/virtualbng.json /root/setup/
CMD /usr/bin/make -C /opt/xos/configurations/cord -f Makefile.inside; /bin/bash
diff --git a/xos/configurations/cord/Makefile b/xos/configurations/cord/Makefile
index 86b4ca1..701e92f 100644
--- a/xos/configurations/cord/Makefile
+++ b/xos/configurations/cord/Makefile
@@ -2,7 +2,7 @@
RUNNING_CONTAINER:=$(shell sudo docker ps|grep "xos"|awk '{print $$NF}')
LAST_CONTAINER=$(shell sudo docker ps -l -q)
-cord: common_cloudlab ceilometer_dashboard
+cord: common_cloudlab ceilometer_dashboard virtualbng_json
echo "# Autogenerated -- do not edit" > Dockerfile
cat ../common/Dockerfile.common Dockerfile.cord >> Dockerfile
cd ../../..; sudo docker build -t xos -f xos/configurations/cord/Dockerfile .
@@ -16,7 +16,10 @@
#NOTE: The below dashboard install scripts assume
#clouldlab openstack environment created using "OpenStack" profile
bash install_xos_ceilometer_dashboard.sh
- bash install_ceilometer_vcpe_notification_listener.sh
+ bash install_ceilometer_patch.sh
+
+virtualbng_json:
+ bash make-virtualbng-json.sh
stop:
sudo docker stop $(RUNNING_CONTAINER)
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 9929a84..02137ec 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -97,20 +97,9 @@
}
}
}
- config_virtualbng.json: >
- {
- "localPublicIpPrefixes" : [
- "10.254.0.128/25"
- ],
- "nextHopIpAddress" : "10.254.0.1",
- "publicFacingMac" : "00:00:00:00:00:66",
- "xosIpAddress" : "10.11.10.1",
- "xosRestPort" : "9999",
- "hosts" : {
- "cp-1.devel.xos-pg0.clemson.cloudlab.us" : "of:0000000000000001/1",
- "cp-2.devel.xos-pg0.clemson.cloudlab.us" : "of:0000000000000001/1"
- }
- }
+ config_virtualbng.json: { get_artifact: [ SELF, virtualbng_json, LOCAL_FILE] }
+ artifacts:
+ virtualbng_json: /root/setup/virtualbng.json
service_ONOS_vOLT:
type: tosca.nodes.ONOSService
@@ -133,7 +122,7 @@
node: service_volt
relationship: tosca.relationships.UsedByService
properties:
- dependencies: org.onosproject.openflow, org.onosproject.olt
+ dependencies: org.onosproject.openflow-base, org.onosproject.olt
config_network-cfg.json: >
{
"devices" : {
diff --git a/xos/configurations/cord/dataplane/dataplane.yaml b/xos/configurations/cord/dataplane/dataplane.yaml
index f43e4d7..489609a 100644
--- a/xos/configurations/cord/dataplane/dataplane.yaml
+++ b/xos/configurations/cord/dataplane/dataplane.yaml
@@ -158,6 +158,14 @@
shell: ifconfig br-lan inet6 del {{ ipv6.stdout }}
when: ipv6.stdout != ""
+ - name: Check if veth1 has an IPv6 address
+ shell: ip addr show veth1|grep inet6|awk '{print $2}'
+ register: ipv6
+
+ - name: Remove veth1 IPv6 address if present
+ shell: ifconfig veth1 inet6 del {{ ipv6.stdout }}
+ when: ipv6.stdout != ""
+
- name: Run the datapath
command: /usr/local/bin/ofdatapath -i veth1,br-lan punix:/tmp/s1 -d 000000000001 --no-slicing -D -P
creates=/usr/local/var/run/ofdatapath.pid
diff --git a/xos/configurations/cord/make-virtualbng-json.sh b/xos/configurations/cord/make-virtualbng-json.sh
new file mode 100644
index 0000000..0e1d350
--- /dev/null
+++ b/xos/configurations/cord/make-virtualbng-json.sh
@@ -0,0 +1,39 @@
+#FN=/opt/xos/configurations/common/cloudlab-nodes.yaml
+FN=virtualbng.json
+
+rm -f $FN
+
+cat >> $FN <<EOF
+{
+ "localPublicIpPrefixes" : [
+ "10.254.0.128/25"
+ ],
+ "nextHopIpAddress" : "10.254.0.1",
+ "publicFacingMac" : "00:00:00:00:00:66",
+ "xosIpAddress" : "10.11.10.1",
+ "xosRestPort" : "9999",
+ "hosts" : {
+EOF
+
+NODES=$( sudo bash -c "source /root/setup/admin-openrc.sh ; nova hypervisor-list" |grep cloudlab|awk '{print $4}' )
+
+NODECOUNT=0
+for NODE in $NODES; do
+ ((NODECOUNT++))
+done
+
+I=0
+for NODE in $NODES; do
+ echo $NODE
+ ((I++))
+ if [[ "$I" -lt "$NODECOUNT" ]]; then
+ echo " \"$NODE\" : \"of:0000000000000001/1\"," >> $FN
+ else
+ echo " \"$NODE\" : \"of:0000000000000001/1\"" >> $FN
+ fi
+done
+
+cat >> $FN <<EOF
+ }
+}
+EOF
diff --git a/xos/configurations/devel/Dockerfile.devel b/xos/configurations/devel/Dockerfile.devel
index 115b731..60c0143 100644
--- a/xos/configurations/devel/Dockerfile.devel
+++ b/xos/configurations/devel/Dockerfile.devel
@@ -4,6 +4,7 @@
ADD xos/configurations/common/flat_net_name /root/setup/
ADD xos/configurations/common/cloudlab-nodes.yaml /opt/xos/configurations/commmon/
ADD xos/configurations/common/id_rsa.pub /root/setup/padmin_public_key
+ADD xos/configurations/common/id_rsa /opt/xos/observers/helloworldservice_complete/helloworldservice_private_key
ADD xos/configurations/common/node_key.pub /root/setup/node_key.pub
ADD xos/configurations/common/node_key /root/setup/node_key
ADD xos/configurations/common/ceilometer_url /root/setup/ceilometer_url
diff --git a/xos/helloworldservice_complete/__init__.py b/xos/helloworldservice_complete/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/xos/helloworldservice_complete/__init__.py
diff --git a/xos/helloworldservice_complete/admin.py b/xos/helloworldservice_complete/admin.py
new file mode 100644
index 0000000..9a3e4f7
--- /dev/null
+++ b/xos/helloworldservice_complete/admin.py
@@ -0,0 +1,141 @@
+
+from core.admin import ReadOnlyAwareAdmin, SliceInline
+from core.middleware import get_request
+from core.models import User
+from django import forms
+from django.contrib import admin
+from helloworldservice_complete.models import HelloWorldServiceComplete, HelloWorldTenantComplete, HELLO_WORLD_KIND
+
+# The class to provide an admin interface on the web for the service.
+# We do only configuration here and don't change any logic because the logic
+# is taken care of for us by ReadOnlyAwareAdmin
+class HelloWorldServiceCompleteAdmin(ReadOnlyAwareAdmin):
+ # We must set the model so that the admin knows what fields to use
+ model = HelloWorldServiceComplete
+ verbose_name = "Hello World Service"
+ verbose_name_plural = "Hello World Services"
+
+ # Setting list_display creates columns on the admin page, each value here
+ # is a column, the column is populated for every instance of the model.
+ list_display = ("backend_status_icon", "name", "enabled")
+
+ # Used to indicate which values in the columns of the admin form are links.
+ list_display_links = ('backend_status_icon', 'name', )
+
+ # Denotes the sections of the form, the fields in the section, and the
+ # CSS classes used to style them. We represent this as a set of tuples, each
+ # tuple as a name (or None) and a set of fields and classes.
+ # Here the first section does not have a name so we use none. That first
+ # section has several fields indicated in the 'fields' attribute, and styled
+ # by the classes indicated in the 'classes' attribute. The classes given
+ # here are important for rendering the tabs on the form. To give the tabs
+ # we must assign the classes suit-tab and suit-tab-<name> where
+ # where <name> will be used later.
+ fieldsets = [(None, {'fields': ['backend_status_text', 'name', 'enabled',
+ 'versionNumber', 'description', "view_url"],
+ 'classes':['suit-tab suit-tab-general']})]
+
+ # Denotes the fields that are readonly and cannot be changed.
+ readonly_fields = ('backend_status_text', )
+
+ # Inlines are used to denote other models that can be edited on the same
+ # form as this one. In this case the service form also allows changes
+ # to slices.
+ inlines = [SliceInline]
+
+ extracontext_registered_admins = True
+
+ # Denotes the fields that can be changed by an admin but not be all users
+ user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
+
+ # Associates fieldsets from this form and from the inlines.
+ # The format here are tuples, of (<name>, tab title). <name> comes from the
+ # <name> in the fieldsets.
+ suit_form_tabs = (('general', 'Hello World Service Details'),
+ ('administration', 'Tenants'),
+ ('slices', 'Slices'),)
+
+ # Used to include a template for a tab. Here we include the
+ # helloworldserviceadmin template in the top position for the administration
+ # tab.
+ suit_form_includes = (('helloworldserviceadmin.html',
+ 'top',
+ 'administration'),)
+
+ # Used to get the objects for this model that are associated with the
+ # requesting user.
+ def queryset(self, request):
+ return HelloWorldServiceComplete.get_service_objects_by_user(request.user)
+
+# Class to represent the form to add and edit tenants.
+# We need to define this instead of just using an admin like we did for the
+# service because tenants vary more than services and there isn't a common form.
+# This allows us to change the python behavior for the admin form to save extra
+# fields and control defaults.
+class HelloWorldTenantCompleteForm(forms.ModelForm):
+ # Defines a field for the creator of this service. It is a dropdown which
+ # is populated with all of the users.
+ creator = forms.ModelChoiceField(queryset=User.objects.all())
+ # Defines a text field for the display message, it is not required.
+ display_message = forms.CharField(required=False)
+
+ def __init__(self, *args, **kwargs):
+ super(HelloWorldTenantCompleteForm, self).__init__(*args, **kwargs)
+ # Set the kind field to readonly
+ self.fields['kind'].widget.attrs['readonly'] = True
+ # Define the logic for obtaining the objects for the provider_service
+ # dropdown of the tenant form.
+ self.fields[
+ 'provider_service'].queryset = HelloWorldServiceComplete.get_service_objects().all()
+ # Set the initial kind to HELLO_WORLD_KIND for this tenant.
+ self.fields['kind'].initial = HELLO_WORLD_KIND
+ # If there is an instance of this model then we can set the initial
+ # form values to the existing values.
+ if self.instance:
+ self.fields['creator'].initial = self.instance.creator
+ self.fields[
+ 'display_message'].initial = self.instance.display_message
+
+ # If there is not an instance then we need to set initial values.
+ if (not self.instance) or (not self.instance.pk):
+ self.fields['creator'].initial = get_request().user
+ if HelloWorldServiceComplete.get_service_objects().exists():
+ self.fields["provider_service"].initial = HelloWorldServiceComplete.get_service_objects().all()[0]
+
+ # This function describes what happens when the save button is pressed on
+ # the tenant form. In this case we set the values for the instance that were
+ # entered.
+ def save(self, commit=True):
+ self.instance.creator = self.cleaned_data.get("creator")
+ self.instance.display_message = self.cleaned_data.get(
+ "display_message")
+ return super(HelloWorldTenantCompleteForm, self).save(commit=commit)
+
+ class Meta:
+ model = HelloWorldTenantComplete
+
+# Define the admin form for the tenant. This uses a similar structure as the
+# service but uses HelloWorldTenantCompleteForm to change the python behavior.
+
+
+class HelloWorldTenantCompleteAdmin(ReadOnlyAwareAdmin):
+ verbose_name = "Hello World Tenant"
+ verbose_name_plural = "Hello World Tenants"
+ list_display = ('id', 'backend_status_icon', 'instance', 'display_message')
+ list_display_links = ('backend_status_icon', 'instance', 'display_message',
+ 'id')
+ fieldsets = [(None, {'fields': ['backend_status_text', 'kind',
+ 'provider_service', 'instance', 'creator',
+ 'display_message'],
+ 'classes': ['suit-tab suit-tab-general']})]
+ readonly_fields = ('backend_status_text', 'instance',)
+ form = HelloWorldTenantCompleteForm
+
+ suit_form_tabs = (('general', 'Details'),)
+
+ def queryset(self, request):
+ return HelloWorldTenantComplete.get_tenant_objects_by_user(request.user)
+
+# Associate the admin forms with the models.
+admin.site.register(HelloWorldServiceComplete, HelloWorldServiceCompleteAdmin)
+admin.site.register(HelloWorldTenantComplete, HelloWorldTenantCompleteAdmin)
diff --git a/xos/helloworldservice_complete/models.py b/xos/helloworldservice_complete/models.py
new file mode 100644
index 0000000..8a4ce59
--- /dev/null
+++ b/xos/helloworldservice_complete/models.py
@@ -0,0 +1,113 @@
+from core.models import Service, TenantWithContainer
+from django.db import transaction
+
+HELLO_WORLD_KIND = "helloworldservice_complete"
+
+# The class to represent the service. Most of the service logic is given for us
+# in the Service class but, we have some configuration that is specific for
+# this example.
+class HelloWorldServiceComplete(Service):
+ KIND = HELLO_WORLD_KIND
+
+ class Meta:
+ # When the proxy field is set to True the model is represented as
+ # it's superclass in the database, but we can still change the python
+ # behavior. In this case HelloWorldServiceComplete is a Service in the
+ # database.
+ proxy = True
+ # The name used to find this service, all directories are named this
+ app_label = "helloworldservice_complete"
+ verbose_name = "Hello World Service"
+
+# This is the class to represent the tenant. Most of the logic is given to use
+# in TenantWithContainer, however there is some configuration and logic that
+# we need to define for this example.
+class HelloWorldTenantComplete(TenantWithContainer):
+
+ class Meta:
+ # Same as a above, HelloWorldTenantComplete is represented as a
+ # TenantWithContainer, but we change the python behavior.
+ proxy = True
+ verbose_name = "Hello World Tenant"
+
+ # The kind of the service is used on forms to differentiate this service
+ # from the other services.
+ KIND = HELLO_WORLD_KIND
+
+ # Ansible requires that the sync_attributes field contain nat_ip and nat_mac
+ # these will be used to determine where to SSH to for ansible.
+ # Getters must be defined for every attribute specified here.
+ sync_attributes = ("nat_ip", "nat_mac",)
+
+ # default_attributes is used cleanly indicate what the default values for
+ # the fields are.
+ default_attributes = {'display_message': 'Hello World!'}
+
+ def __init__(self, *args, **kwargs):
+ helloworld_services = HelloWorldServiceComplete.get_service_objects().all()
+ # When the tenant is created the default service in the form is set
+ # to be the first created HelloWorldServiceComplete
+ if helloworld_services:
+ self._meta.get_field(
+ "provider_service").default = helloworld_services[0].id
+ super(HelloWorldTenantComplete, self).__init__(*args, **kwargs)
+
+ def save(self, *args, **kwargs):
+ super(HelloWorldTenantComplete, self).save(*args, **kwargs)
+ # This call needs to happen so that an instance is created for this
+ # tenant is created in the slice. One instance is created per tenant.
+ model_policy_helloworld_tenant(self.pk)
+
+ def delete(self, *args, **kwargs):
+ # Delete the instance that was created for this tenant
+ self.cleanup_container()
+ super(HelloWorldTenantComplete, self).delete(*args, **kwargs)
+
+ # Getter for the message that will appear on the webpage
+ # By default it is "Hello World!"
+ @property
+ def display_message(self):
+ return self.get_attribute(
+ "display_message",
+ self.default_attributes['display_message'])
+
+ # Setter for the message that will appear on the webpage
+ @display_message.setter
+ def display_message(self, value):
+ self.set_attribute("display_message", value)
+
+ @property
+ def addresses(self):
+ if (not self.id) or (not self.instance):
+ return {}
+
+ addresses = {}
+ # The ports field refers to networks for the instance.
+ # This loop stores the details for the NAT network that will be
+ # necessary for ansible.
+ for ns in self.instance.ports.all():
+ if "nat" in ns.network.name.lower():
+ addresses["nat"] = (ns.ip, ns.mac)
+ return addresses
+
+ # This getter is necessary because nat_ip is a sync_attribute
+ @property
+ def nat_ip(self):
+ return self.addresses.get("nat", (None, None))[0]
+
+ # This getter is necessary because nat_mac is a sync_attribute
+ @property
+ def nat_mac(self):
+ return self.addresses.get("nat", (None, None))[1]
+
+
+def model_policy_helloworld_tenant(pk):
+ # This section of code is atomic to prevent race conditions
+ with transaction.atomic():
+ # We find all of the tenants that are waiting to update
+ tenant = HelloWorldTenantComplete.objects.select_for_update().filter(pk=pk)
+ if not tenant:
+ return
+ # Since this code is atomic it is safe to always use the first tenant
+ tenant = tenant[0]
+ tenant.manage_container()
diff --git a/xos/helloworldservice_complete/templates/helloworldserviceadmin.html b/xos/helloworldservice_complete/templates/helloworldserviceadmin.html
new file mode 100644
index 0000000..ba418ee
--- /dev/null
+++ b/xos/helloworldservice_complete/templates/helloworldserviceadmin.html
@@ -0,0 +1,10 @@
+<!-- Template used to for the button leading to the HelloWorldTenantComplete form. -->
+<div class = "left-nav">
+ <ul>
+ <li>
+ <a href="/admin/helloworldservice_complete/helloworldtenantcomplete/">
+ Hello World Tenants
+ </a>
+ </li>
+ </ul>
+</div>
diff --git a/xos/manage.py b/xos/manage.py
index 0ddd014..5d09794 100644
--- a/xos/manage.py
+++ b/xos/manage.py
@@ -7,6 +7,10 @@
from django.core.management import execute_from_command_line
+ if "--makemigrations" in sys.argv:
+ os.system("/opt/xos/scripts/opencloud makemigrations")
+ sys.argv.remove("--makemigrations")
+
if "--nomodelpolicy" in sys.argv:
import model_policy
model_policy.EnableModelPolicy(False)
diff --git a/xos/observers/helloworldservice_complete/helloworldservice-observer.py b/xos/observers/helloworldservice_complete/helloworldservice-observer.py
new file mode 100755
index 0000000..75dcc46
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/helloworldservice-observer.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+# This imports and runs ../../xos-observer.py
+# Runs the standard XOS observer
+
+import importlib
+import os
+import sys
+observer_path = os.path.join(os.path.dirname(
+ os.path.realpath(__file__)), "../..")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-observer")
+mod.main()
diff --git a/xos/observers/helloworldservice_complete/helloworldservice_config b/xos/observers/helloworldservice_complete/helloworldservice_config
new file mode 100644
index 0000000..716e3a0
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/helloworldservice_config
@@ -0,0 +1,36 @@
+# Required by XOS
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+# Required by XOS
+[api]
+nova_enabled=True
+
+# Sets options for the observer
+[observer]
+# Optional name
+name=helloworldservice
+# This is the location to the dependency graph you generate
+dependency_graph=/opt/xos/observers/helloworldservice_complete/model-deps
+# The location of your SyncSteps
+steps_dir=/opt/xos/observers/helloworldservice_complete/steps
+# A temporary directory that will be used by ansible
+sys_dir=/opt/xos/observers/helloworldservice_complete/sys
+# Location of the file to save logging messages to the backend log is often used
+logfile=/var/log/xos_backend.log
+# If this option is true, then nothing will change, we simply pretend to run
+pretend=False
+# If this is False then XOS will use an exponential backoff when the observer
+# fails, since we will be waiting for an instance, we don't want this.
+backoff_disabled=True
+# We want the output from ansible to be logged
+save_ansible_output=True
+# This determines how we SSH to a client, if this is set to True then we try
+# to ssh using the instance name as a proxy, if this is disabled we ssh using
+# the NAT IP of the instance. On CloudLab the first option will fail so we must
+# set this to False
+proxy_ssh=False
diff --git a/xos/observers/helloworldservice_complete/model-deps b/xos/observers/helloworldservice_complete/model-deps
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/model-deps
@@ -0,0 +1 @@
+{}
diff --git a/xos/observers/helloworldservice_complete/run.sh b/xos/observers/helloworldservice_complete/run.sh
new file mode 100755
index 0000000..6bce079
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/run.sh
@@ -0,0 +1,3 @@
+# Runs the XOS observer using helloworldservice_config
+export XOS_DIR=/opt/xos
+python helloworldservice-observer.py -C $XOS_DIR/observers/helloworldservice_complete/helloworldservice_config
diff --git a/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.py b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.py
new file mode 100644
index 0000000..a4b5435
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.py
@@ -0,0 +1,48 @@
+import os
+import sys
+from django.db.models import Q, F
+from helloworldservice_complete.models import HelloWorldServiceComplete, HelloWorldTenantComplete
+from observers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+
+parentdir = os.path.join(os.path.dirname(__file__), "..")
+sys.path.insert(0, parentdir)
+
+# Class to define how we sync a tenant. Using SyncInstanceUsingAnsible we
+# indicate where the find the YAML for ansible, where to find the SSH key,
+# and the logic for determining what tenant needs updating, what additional
+# attributes are needed, and how to delete an instance.
+class SyncHelloWorldTenantComplete(SyncInstanceUsingAnsible):
+ # Indicates the position in the data model, this will run when XOS needs to
+ # enact a HelloWorldTenantComplete
+ provides = [HelloWorldTenantComplete]
+ # The actual model being enacted, usually the same as provides.
+ observes = HelloWorldTenantComplete
+ # Number of milliseconds between interruptions of the observer
+ requested_interval = 0
+ # The ansible template to run
+ template_name = "sync_helloworldtenant.yaml"
+ # The location of the SSH private key to use when ansible connects to
+ # instances.
+ service_key_name = "/opt/xos/observers/helloworldservice_complete/helloworldservice_private_key"
+
+ def __init__(self, *args, **kwargs):
+ super(SyncHelloWorldTenantComplete, self).__init__(*args, **kwargs)
+
+ # Defines the logic for determining what HelloWorldTenantCompletes need to be
+ # enacted.
+ def fetch_pending(self, deleted):
+ # If the update is not a deletion, then we get all of the instnaces that
+ # have been updated or have not been enacted.
+ if (not deleted):
+ objs = HelloWorldTenantComplete.get_tenant_objects().filter(
+ Q(enacted__lt=F('updated')) | Q(enacted=None), Q(lazy_blocked=False))
+ else:
+ # If this is a deletion we get all of the deleted tenants..
+ objs = HelloWorldTenantComplete.get_deleted_tenant_objects()
+
+ return objs
+
+ # Gets the attributes that are used by the Ansible template but are not
+ # part of the set of default attributes.
+ def get_extra_attributes(self, o):
+ return {"display_message": o.display_message}
diff --git a/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.yaml b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.yaml
new file mode 100644
index 0000000..719c75f
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.yaml
@@ -0,0 +1,18 @@
+---
+- hosts: {{ instance_name }}
+ gather_facts: False
+ connection: ssh
+ user: ubuntu
+ sudo: yes
+ tasks:
+ - name: install apache
+ apt: name=apache2 state=present update_cache=yes
+
+ - name: write message
+ shell: echo "{{ display_message }}" > /var/www/html/index.html
+
+ - name: stop apache
+ service: name=apache2 state=stopped
+
+ - name: start apache
+ service: name=apache2 state=started
diff --git a/xos/observers/helloworldservice_complete/stop.sh b/xos/observers/helloworldservice_complete/stop.sh
new file mode 100755
index 0000000..76e68d9
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/stop.sh
@@ -0,0 +1,2 @@
+# Kill the observer
+pkill -9 -f helloworldservice-observer.py
diff --git a/xos/observers/onos/steps/sync_onosapp.py b/xos/observers/onos/steps/sync_onosapp.py
index 8c97391..9b32298 100644
--- a/xos/observers/onos/steps/sync_onosapp.py
+++ b/xos/observers/onos/steps/sync_onosapp.py
@@ -75,6 +75,7 @@
def write_configs(self, o):
o.config_fns = []
+ o.rest_configs = []
o.files_dir = self.get_files_dir(o)
if not os.path.exists(o.files_dir):
@@ -85,6 +86,16 @@
fn = attr.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:]
+ # 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)
+ o.rest_configs.append( {"endpoint": endpoint, "fn": fn} )
def prepare_record(self, o):
self.write_configs(o)
@@ -95,6 +106,7 @@
fields["appname"] = o.name
fields["nat_ip"] = self.get_instance(o).get_ssh_ip()
fields["config_fns"] = o.config_fns
+ fields["rest_configs"] = o.rest_configs
fields["dependencies"] = [x.strip() for x in o.dependencies.split(",")]
fields["ONOS_container"] = "ONOS"
return fields
diff --git a/xos/observers/onos/steps/sync_onosapp.yaml b/xos/observers/onos/steps/sync_onosapp.yaml
index ad3718c..9ee2513 100644
--- a/xos/observers/onos/steps/sync_onosapp.yaml
+++ b/xos/observers/onos/steps/sync_onosapp.yaml
@@ -7,6 +7,13 @@
vars:
appname: {{ appname }}
dependencies: {{ dependencies }}
+{% if rest_configs %}
+ rest_configs:
+{% for rest_config in rest_configs %}
+ - endpoint: {{ rest_config.endpoint }}
+ body: "{{ '{{' }} lookup('file', '{{ files_dir }}/{{ rest_config.fn }}') {{ '}}' }}"
+{% endfor %}
+{% endif %}
tasks:
@@ -38,6 +45,18 @@
{% 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
diff --git a/xos/scripts/opencloud b/xos/scripts/opencloud
index 189a673..6b3e737 100755
--- a/xos/scripts/opencloud
+++ b/xos/scripts/opencloud
@@ -144,6 +144,7 @@
python ./manage.py makemigrations syndicate_storage
python ./manage.py makemigrations cord
python ./manage.py makemigrations ceilometer
+ python ./manage.py makemigrations helloworldservice_complete
python ./manage.py makemigrations onos
#python ./manage.py makemigrations servcomp
}
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 4f726fc..d4e96ce 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -143,6 +143,9 @@
config_network-cfg.json:
type: string
required: false
+ rest_onos/v1/network/configuration/:
+ type: string
+ required: false
tosca.nodes.VCPEService:
description: >
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 246c922..0c20211 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -165,6 +165,9 @@
config_network-cfg.json:
type: string
required: false
+ rest_onos/v1/network/configuration/:
+ type: string
+ required: false
tosca.nodes.VCPEService:
description: >
diff --git a/xos/tosca/resources/onosapp.py b/xos/tosca/resources/onosapp.py
index 648bb09..7ed47d7 100644
--- a/xos/tosca/resources/onosapp.py
+++ b/xos/tosca/resources/onosapp.py
@@ -57,6 +57,8 @@
v = d.value
if k.startswith("config_"):
self.set_tenant_attr(obj, k, v)
+ elif k.startswith("rest_"):
+ self.set_tenant_attr(obj, k, v)
def can_delete(self, obj):
return super(XOSONOSApp, self).can_delete(obj)
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index 5942d2c..e276954 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -1,15 +1,36 @@
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP
from django import VERSION as DJANGO_VERSION
import socket
+import os
+from urlparse import urlparse
# Django settings for XOS.
from config import Config
+from config import set_override
config = Config()
+# Override the config from the environment. This is used leverage the LINK
+# capability of docker. It would be far better to use DNS and that can be
+# done in environments like kubernetes. Look for environment variables that
+# match the link pattern and set the appropriate overeides. It is expected
+# that the set of overrides will be expanded with need
+def overrideDbSettings(v):
+ parsed = urlparse(v)
+ config.db_host = parsed.hostname
+ config.db_port = parsed.port
+
+env_to_config_dict = {
+ "XOS_DB_PORT" : overrideDbSettings
+}
+
+for key, ofunc in env_to_config_dict.items():
+ if key in os.environ:
+ ofunc(os.environ[key])
+
GEOIP_PATH = "/usr/share/GeoIP"
XOS_DIR = "/opt/xos"
-
-DEBUG = False
+
+DEBUG = False
TEMPLATE_DEBUG = DEBUG
ADMINS = (
@@ -29,7 +50,7 @@
'USER': config.db_user,
'PASSWORD': config.db_password,
'HOST': config.db_host, # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
- 'PORT': '', # Set to empty string for default.
+ 'PORT': config.db_port, # Set to empty string for default.
}
}
@@ -132,7 +153,7 @@
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
XOS_DIR + "/templates",
- XOS_DIR + "/core/xoslib/templates",
+ XOS_DIR + "/core/xoslib/templates",
)
INSTALLED_APPS = (
@@ -154,6 +175,7 @@
'core',
'hpc',
'cord',
+ 'helloworldservice_complete',
'services.onos',
'ceilometer',
'requestrouter',
@@ -169,7 +191,7 @@
INSTALLED_APPS = list(INSTALLED_APPS)
INSTALLED_APPS[INSTALLED_APPS.index('django.contrib.admin')] = 'django.contrib.admin.apps.SimpleAdminConfig'
-# Added for django-suit form
+# Added for django-suit form
TEMPLATE_CONTEXT_PROCESSORS = TCP + (
'django.core.context_processors.request',
'core.context_processors.xos',
@@ -221,4 +243,3 @@
# prevents warnings on django 1.7
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
-