Creating the new rest container
Defining XOS Components in the form of containers

Change-Id: Id0c982e8ad5dc51a37462eb5ef55fa50a4be23fb
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index 4138b22..de3a941 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -5,6 +5,7 @@
 from .service import Service, Tenant, TenantWithContainer, CoarseTenant, ServicePrivilege, TenantRoot, TenantRootPrivilege, TenantRootRole, TenantPrivilege, TenantRole, Subscriber, Provider
 from .service import ServiceAttribute, TenantAttribute, ServiceRole
 from .service import ServiceController, ServiceControllerResource, LoadableModule, LoadableModuleResource, Library
+from .service import XOSComponent, XOSComponentLink, XOSComponentVolume
 from .tag import Tag
 from .role import Role
 from .site import Site, Deployment, DeploymentRole, DeploymentPrivilege, Controller, ControllerRole, ControllerSite, SiteDeployment,Diag
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 894033a..8f874e3 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -193,6 +193,36 @@
     # for now, it's exactly like a LoadableModule
     pass
 
+
+class XOSComponent(LoadableModule):
+    # this will define loadable XOS component in the form of containers
+    image = StrippedCharField(max_length=200, help_text="docker image name")
+    command = StrippedCharField(max_length=1024, help_text="docker run command", null=True, blank=True)
+    ports = StrippedCharField(max_length=200, help_text="port binding", null=True, blank=True)
+
+
+class XOSComponentLink(PlCoreBase):
+
+    LINK_KIND = (
+        ('internal', 'Internal'),
+        ('external', 'External')
+    )
+
+    component = models.ForeignKey(XOSComponent, related_name='links', help_text="The Component object for this Link")
+    container = StrippedCharField(max_length=200, help_text="container to link")
+    alias = StrippedCharField(max_length=200, help_text="alias for the link")
+    kind = models.CharField(max_length=20, choices=LINK_KIND, default='internal')
+
+
+# NOTE can this be the same of XOSVolume??
+class XOSComponentVolume(PlCoreBase):
+    component = models.ForeignKey(XOSComponent, related_name='volumes', help_text="The Component object for this Volume")
+    name = StrippedCharField(max_length=30, help_text="Volume Name")
+    container_path = StrippedCharField(max_length=1024, unique=True, help_text="Path of Volume in Container")
+    host_path = StrippedCharField(max_length=1024, help_text="Path of Volume in Host")
+    read_only = models.BooleanField(default=False, help_text="True if mount read-only")
+
+
 class ServiceController(LoadableModule):
     synchronizer_run = StrippedCharField(max_length=1024, help_text="synchronizer run command", null=True, blank=True)
     synchronizer_config = StrippedCharField(max_length=1024, help_text="synchronizer config file", null=True, blank=True)
diff --git a/xos/synchronizers/onboarding/steps/sync_component.py b/xos/synchronizers/onboarding/steps/sync_component.py
new file mode 100644
index 0000000..128c98f
--- /dev/null
+++ b/xos/synchronizers/onboarding/steps/sync_component.py
@@ -0,0 +1,47 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep, DeferredException
+from core.models import XOS, XOSComponent
+from xos.logger import Logger, logging
+from synchronizers.base.ansible import run_template
+
+# xosbuilder will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from xosbuilder import XOSBuilder
+
+logger = Logger(level=logging.INFO)
+
+class SyncXOSComponent(SyncStep, XOSBuilder):
+    provides=[XOSComponent]
+    observes=XOSComponent
+    requested_interval=0
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        XOSBuilder.__init__(self)
+
+    def sync_record(self, sc):
+        logger.info("Sync'ing XOSComponent %s" % sc)
+
+        if sc.xos and (not sc.xos.enable_build):
+            raise DeferredException("XOS build is currently disabled")
+
+        unready = self.check_controller_unready(sc)
+        if unready:
+            raise Exception("Controller %s has unready resources: %s" % (str(sc), ",".join([str(x) for x in unready])))
+
+        # There's nothing to actually do, since there's no synchronizer
+        # container for libraries.
+
+    def delete_record(self, m):
+        pass
+
+    def fetch_pending(self, deleted=False):
+        pend = super(SyncXOSComponent, self).fetch_pending(deleted)
+        return pend
+
diff --git a/xos/synchronizers/onboarding/xosbuilder.py b/xos/synchronizers/onboarding/xosbuilder.py
index c23f996..c791f1f 100644
--- a/xos/synchronizers/onboarding/xosbuilder.py
+++ b/xos/synchronizers/onboarding/xosbuilder.py
@@ -8,24 +8,26 @@
 import xmlrpclib
 
 from xos.config import Config
-from core.models import Service, ServiceController, ServiceControllerResource, LoadableModule, LoadableModuleResource, XOS
+from core.models import Service, ServiceController, ServiceControllerResource, LoadableModule, LoadableModuleResource, XOS, XOSComponent
 from xos.logger import Logger, logging
 
 from django.utils import timezone
 
 logger = Logger(level=logging.INFO)
 
+
 def add_unique(list, item):
-   if not item in list:
-       list.append(item)
+    if not item in list:
+        list.append(item)
+
 
 class XOSBuilder(object):
-    UI_KINDS=["models", "admin", "admin_template", "django_library", "rest_service", "rest_tenant", "tosca_custom_types", "tosca_resource","public_key","vendor_js"]
-    SYNC_CONTROLLER_KINDS=["synchronizer", "private_key", "public_key"]
-    SYNC_ALLCONTROLLER_KINDS=["models", "django_library"]
+    UI_KINDS = ["models", "admin", "admin_template", "django_library", "rest_service", "rest_tenant", "tosca_custom_types", "tosca_resource","public_key","vendor_js"]
+    SYNC_CONTROLLER_KINDS = ["synchronizer", "private_key", "public_key"]
+    SYNC_ALLCONTROLLER_KINDS = ["models", "django_library"]
 
     def __init__(self):
-        self.source_sync_image = "xosproject/xos" # "xosproject/xos-synchronizer-openstack"
+        self.source_sync_image = "xosproject/xos"  # "xosproject/xos-synchronizer-openstack"
         self.build_dir = "/opt/xos/BUILD/"
         self.build_tainted = False
 
@@ -74,13 +76,13 @@
         manifest_lines = [x for x in manifest_lines if x]
         for line in manifest_lines:
             url_parts = urlparse.urlsplit(scr.full_url)
-            new_path = os.path.join(os.path.join(*os.path.split(url_parts.path)[:-1]),line)
-            url = urlparse.urlunsplit( (url_parts.scheme, url_parts.netloc, new_path, url_parts.query, url_parts.fragment) )
+            new_path = os.path.join(os.path.join(*os.path.split(url_parts.path)[:-1]), line)
+            url = urlparse.urlunsplit((url_parts.scheme, url_parts.netloc, new_path, url_parts.query, url_parts.fragment))
 
             build_fn = os.path.join(self.get_dest_dir(scr), line)
             download_fn = os.path.join(self.build_dir, build_fn)
 
-            manifest.append( (url, download_fn, build_fn) )
+            manifest.append((url, download_fn, build_fn))
         return manifest
 
     def download_file(self, url, dest_fn):
@@ -88,10 +90,10 @@
         if not os.path.exists(os.path.dirname(dest_fn)):
             os.makedirs(os.path.dirname(dest_fn))
         obj = urllib2.urlopen(url)
-        file(dest_fn,"w").write(obj.read())
+        file(dest_fn, "w").write(obj.read())
 
         # make python files executable
-        if dest_fn.endswith(".py"): # and contents.startswith("#!"):
+        if dest_fn.endswith(".py"):  # and contents.startswith("#!"):
             os.chmod(dest_fn, 0755)
 
     def download_resource(self, scr):
@@ -145,8 +147,8 @@
             manifest = self.read_manifest(scr, manifest_fn)
             lines = []
             for (url, download_fn, build_fn) in manifest:
-               lines.append("mkdir -p /%s" % os.path.dirname(build_fn))
-               lines.append("cp /build/%s /%s" % (build_fn, build_fn))
+                lines.append("mkdir -p /%s" % os.path.dirname(build_fn))
+                lines.append("cp /build/%s /%s" % (build_fn, build_fn))
             return lines
         else:
             build_fn = self.get_build_fn(scr)
@@ -155,8 +157,8 @@
 
     def get_controller_script_lines(self, controller, kinds):
         need_service_init_py = False
-        script=[]
-        inits=[]
+        script = []
+        inits = []
         for scr in list(controller.loadable_module_resources.all()):
             if not (scr.kind in kinds):
                 continue
@@ -191,12 +193,12 @@
                         add_unique(inits, dir)
 
         for init in inits:
-            script.append("echo > %s" % os.path.join("/",init,"__init__.py"))
+            script.append("echo > %s" % os.path.join("/", init, "__init__.py"))
 
         return script
 
     def check_controller_unready(self, controller):
-        unready_resources=[]
+        unready_resources = []
         for scr in controller.loadable_module_resources.all():
             if (not scr.backend_status) or (not scr.backend_status.startswith("1")):
                 unready_resources.append(scr)
@@ -206,19 +208,19 @@
     # stuff that has to do with building
 
     def create_xos_app_data(self, name, script, app_list, migration_list):
-        if not os.path.exists(os.path.join(self.build_dir,"opt/xos/xos")):
-            os.makedirs(os.path.join(self.build_dir,"opt/xos/xos"))
+        if not os.path.exists(os.path.join(self.build_dir, "opt/xos/xos")):
+            os.makedirs(os.path.join(self.build_dir, "opt/xos/xos"))
 
         if app_list:
             script.append("mkdir -p /opt/xos/xos")
             script.append("cp /build/opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
-            #dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
+            # dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
             file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_app_list") % name, "w").write("\n".join(app_list)+"\n")
 
         if migration_list:
             script.append("mkdir -p /opt/xos/xos")
             script.append("cp /build/opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
-            #dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
+            # dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
             file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_migration_list") % name, "w").write("\n".join(migration_list)+"\n")
 
     def create_ui_dockerfile(self):
@@ -233,10 +235,10 @@
         script = []
         for controller in LoadableModule.objects.all():
             if self.check_controller_unready(controller):
-                 logger.warning("Loadable Module %s has unready resources" % str(controller))
-                 continue
+                logger.warning("Loadable Module %s has unready resources" % str(controller))
+                continue
 
-            #dockerfile = dockerfile + self.get_controller_docker_lines(controller, self.UI_KINDS)
+            # dockerfile = dockerfile + self.get_controller_docker_lines(controller, self.UI_KINDS)
             script = script + self.get_controller_script_lines(controller, self.UI_KINDS)
             if controller.loadable_module_resources.filter(kind="models").exists():
                 app_list.append("services." + controller.name)
@@ -304,42 +306,43 @@
                 "docker_image_name": "xosproject/xos-synchronizer-%s" % controller.name}
 
     def create_docker_compose(self):
-         xos = XOS.objects.all()[0]
+        xos = XOS.objects.all()[0]
 
-         volume_list = []
-         for volume in xos.volumes.all():
-             volume_list.append({"host_path": volume.host_path,
-                                 "container_path": volume.container_path,
-                                 "read_only": volume.read_only})
+        volume_list = []
+        for volume in xos.volumes.all():
+            volume_list.append({"host_path": volume.host_path,
+                                "container_path": volume.container_path,
+                                "read_only": volume.read_only})
 
-         if xos.extra_hosts:
-             extra_hosts = [x.strip() for x in xos.extra_hosts.split(",")]
-         else:
-             extra_hosts = []
+        if xos.extra_hosts:
+            extra_hosts = [x.strip() for x in xos.extra_hosts.split(",")]
+        else:
+            extra_hosts = []
 
-         containers = {}
+        containers = {}
 
 #         containers["xos_db"] = \
 #                            {"image": "xosproject/xos-postgres",
 #                             "expose": [5432]}
 
-         external_links=[]
-         if xos.db_container_name:
-             external_links.append("%s:%s" % (xos.db_container_name, "xos_db"))
-         if xos.redis_container_name:
-             external_links.append("%s:%s" % (xos.redis_container_name, "redis"))
+        external_links = []
+        if xos.db_container_name:
+            external_links.append("%s:%s" % (xos.db_container_name, "xos_db"))
+        if xos.redis_container_name:
+            external_links.append("%s:%s" % (xos.redis_container_name, "redis"))
 
-         containers["xos_ui"] = \
-                            {"image": "xosproject/xos-ui",
-                             "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.ui_port,
-                             "ports": {"%d"%xos.ui_port : "%d"%xos.ui_port},
-                             #"links": ["xos_db"],
-                             "external_links": external_links,
-                             "extra_hosts": extra_hosts,
-                             "volumes": volume_list}
+        containers["xos_ui"] = {
+            "image": "xosproject/xos-ui",
+            "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.ui_port,
+            "ports": {"%d" % xos.ui_port: "%d" % xos.ui_port},
+            # "links": ["xos_db"],
+            # "external_links": ["%s:%s" % (xos.db_container_name, "xos_db")],
+            "external_links": external_links,
+            "extra_hosts": extra_hosts,
+            "volumes": volume_list}
 
-         if xos.no_start:
-             containers["xos_ui"]["command"] = "sleep 864000"
+        if xos.no_start:
+            containers["xos_ui"]["command"] = "sleep 864000"
 
 #         containers["xos_bootstrap_ui"] = {"image": "xosproject/xos",
 #                             "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.bootstrap_ui_port,
@@ -348,38 +351,70 @@
 #                             "links": ["xos_db"],
 #                             "volumes": volume_list}
 
-         if not xos.frontend_only:
-             for c in ServiceController.objects.all():
-                 if self.check_controller_unready(c):
-                     logger.warning("Controller %s has unready resources" % str(c))
-                     continue
+        # creating Component containers
+        for c in XOSComponent.objects.all():
 
-                 if c.loadable_module_resources.filter(kind="synchronizer").exists():
-                     if c.synchronizer_run and c.synchronizer_config:
-                         command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; python ./%s -C %s"' % (c.name, c.synchronizer_run, c.synchronizer_config)
-                     else:
-                         command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; bash ./run.sh"' % c.name
+            # create internal and external links list
+            links = []
+            external_links = []
+            for l in c.links.all():
+                if l.kind == 'internal':
+                    links.append("%s:%s" % (l.container, l.alias))
+                elif l.kind == 'external':
+                    external_links.append("%s:%s" % (l.container, l.alias))
 
-                     containers["xos_synchronizer_%s" % c.name] = \
-                                    {"image": "xosproject/xos-synchronizer-%s" % c.name,
-                                     "command": command,
-                                     "external_links": external_links,
-                                     "extra_hosts": extra_hosts,
-                                     "volumes": volume_list}
+            # creating volumes list
+            volume_list = []
+            for volume in c.volumes.all():
+                volume_list.append({"host_path": volume.host_path,
+                                    "container_path": volume.container_path,
+                                    "read_only": volume.read_only})
 
-                     if c.no_start:
-                         containers["xos_synchronizer_%s" % c.name]["command"] = "sleep 864000"
+            port = c.ports.split(":")
+            containers[c.name] = {
+                "image": c.image,
+                "command": c.command,
+                "ports": {
+                    port[0]: port[1]
+                },
+                "links": links,
+                "external_links": external_links,
+                "volumes": volume_list
+            }
 
-         vars = { "containers": containers }
+        if not xos.frontend_only:
+            for c in ServiceController.objects.all():
+                if self.check_controller_unready(c):
+                    logger.warning("Controller %s has unready resources" % str(c))
+                    continue
 
-         template_loader = jinja2.FileSystemLoader( "/opt/xos/synchronizers/onboarding/templates/" )
-         template_env = jinja2.Environment(loader=template_loader)
-         template = template_env.get_template("docker-compose.yml.j2")
-         buffer = template.render(vars)
+                if c.loadable_module_resources.filter(kind="synchronizer").exists():
+                    if c.synchronizer_run and c.synchronizer_config:
+                        command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; python ./%s -C %s"' % (c.name, c.synchronizer_run, c.synchronizer_config)
+                    else:
+                        command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; bash ./run.sh"' % c.name
 
-         if not os.path.exists("/opt/xos/synchronizers/onboarding/docker-compose"):
-             os.makedirs("/opt/xos/synchronizers/onboarding/docker-compose")
-         file("/opt/xos/synchronizers/onboarding/docker-compose/docker-compose.yml", "w").write(buffer)
+                    containers["xos_synchronizer_%s" % c.name] = {
+                        "image": "xosproject/xos-synchronizer-%s" % c.name,
+                        "command": command,
+                        "external_links": ["%s:%s" % (xos.db_container_name, "xos_db")],
+                        "extra_hosts": extra_hosts,
+                        "volumes": volume_list
+                    }
+
+                    if c.no_start:
+                        containers["xos_synchronizer_%s" % c.name]["command"] = "sleep 864000"
+
+        vars = {"containers": containers}
+
+        template_loader = jinja2.FileSystemLoader("/opt/xos/synchronizers/onboarding/templates/")
+        template_env = jinja2.Environment(loader=template_loader)
+        template = template_env.get_template("docker-compose.yml.j2")
+        buffer = template.render(vars)
+
+        if not os.path.exists("/opt/xos/synchronizers/onboarding/docker-compose"):
+            os.makedirs("/opt/xos/synchronizers/onboarding/docker-compose")
+        file("/opt/xos/synchronizers/onboarding/docker-compose/docker-compose.yml", "w").write(buffer)
 
 #    def build_xos(self):
 #        dockerfiles=[]
@@ -387,6 +422,3 @@
 #
 #        for controller in ServiceController.objects.all():
 #            dockerfiles.append(self.create_synchronizer_dockerfile(controller))
-
-
-
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 4cbf675..b0d5b32 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -48,7 +48,6 @@
                 required: false
                 description: List of extra_hosts to pass to docker compose
 
-
     tosca.nodes.XOSVolume:
         derived_from: tosca.nodes.Root
         description: A volume that should be attached to the XOS docker container
@@ -239,6 +238,73 @@
                 required: false
                 description: third-party javascript files
 
+    tosca.nodes.Component:
+        derived_from: tosca.nodes.Root
+        description: >
+            An XOS Component in the form of a docker container.
+        properties:
+            xos_base_props
+            name:
+                type: string
+                required: true
+                description: the container name
+            image:
+                type: string
+                required: true
+                description: the base image for the container
+            command:
+                type: string
+                required: false
+                description: the command to execute in the container
+            ports:
+                type: string
+                required: false
+                description: ports that need to be exposed
+
+    tosca.nodes.ComponentLink:
+        derived_from: tosca.nodes.Root
+        description: >
+            Links between XOS components.
+        properties:
+            xos_base_props
+            container:
+                type: string
+                required: true
+                description: the container that needs to be linked
+            alias:
+                type: string
+                required: true
+                description: alias for the link
+            kind:
+                type: string
+                required: true
+                description: internal or external link
+                constraints:
+                    - valid_values: [ 'internal', 'external' ]
+
+    tosca.nodes.ComponentVolume:
+        derived_from: tosca.nodes.Root
+        description: >
+            Volumes of the XOS components.
+        properties:
+            xos_base_props
+            host_path:
+                type: string
+                required: false
+                description: path of resource on host
+            read_only:
+                type: boolean
+                required: false
+                description: True if mount read only
+
+    tosca.relationships.LinkOfComponent:
+            derived_from: tosca.relationships.Root
+            valid_target_types: [ tosca.capabilities.xos.ComponentLink ]
+
+    tosca.relationships.VolumeOfComponent:
+            derived_from: tosca.relationships.Root
+            valid_target_types: [ tosca.capabilities.xos.ComponentVolume ]
+
     tosca.nodes.Tenant:
         derived_from: tosca.nodes.Root
         description: >
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index fc9eb42..d3baf30 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -78,7 +78,6 @@
                 required: false
                 description: List of extra_hosts to pass to docker compose
 
-
     tosca.nodes.XOSVolume:
         derived_from: tosca.nodes.Root
         description: A volume that should be attached to the XOS docker container
@@ -377,6 +376,118 @@
                 required: false
                 description: third-party javascript files
 
+    tosca.nodes.Component:
+        derived_from: tosca.nodes.Root
+        description: >
+            An XOS Component in the form of a docker container.
+        properties:
+            no-delete:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to delete this object
+            no-create:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to create this object
+            no-update:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to update this object
+            replaces:
+                type: string
+                required: false
+                descrption: Replaces/renames this object
+            name:
+                type: string
+                required: true
+                description: the container name
+            image:
+                type: string
+                required: true
+                description: the base image for the container
+            command:
+                type: string
+                required: false
+                description: the command to execute in the container
+            ports:
+                type: string
+                required: false
+                description: ports that need to be exposed
+
+    tosca.nodes.ComponentLink:
+        derived_from: tosca.nodes.Root
+        description: >
+            Links between XOS components.
+        properties:
+            no-delete:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to delete this object
+            no-create:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to create this object
+            no-update:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to update this object
+            replaces:
+                type: string
+                required: false
+                descrption: Replaces/renames this object
+            container:
+                type: string
+                required: true
+                description: the container that needs to be linked
+            alias:
+                type: string
+                required: true
+                description: alias for the link
+            kind:
+                type: string
+                required: true
+                description: internal or external link
+                constraints:
+                    - valid_values: [ 'internal', 'external' ]
+
+    tosca.nodes.ComponentVolume:
+        derived_from: tosca.nodes.Root
+        description: >
+            Volumes of the XOS components.
+        properties:
+            no-delete:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to delete this object
+            no-create:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to create this object
+            no-update:
+                type: boolean
+                default: false
+                description: Do not allow Tosca to update this object
+            replaces:
+                type: string
+                required: false
+                descrption: Replaces/renames this object
+            host_path:
+                type: string
+                required: false
+                description: path of resource on host
+            read_only:
+                type: boolean
+                required: false
+                description: True if mount read only
+
+    tosca.relationships.LinkOfComponent:
+            derived_from: tosca.relationships.Root
+            valid_target_types: [ tosca.capabilities.xos.ComponentLink ]
+
+    tosca.relationships.VolumeOfComponent:
+            derived_from: tosca.relationships.Root
+            valid_target_types: [ tosca.capabilities.xos.ComponentVolume ]
+
     tosca.nodes.Tenant:
         derived_from: tosca.nodes.Root
         description: >
diff --git a/xos/tosca/resources/xoscomponent.py b/xos/tosca/resources/xoscomponent.py
new file mode 100644
index 0000000..f74cfde
--- /dev/null
+++ b/xos/tosca/resources/xoscomponent.py
@@ -0,0 +1,40 @@
+from xosresource import XOSResource
+from core.models import XOSComponent, XOSComponentLink, XOSComponentVolume
+
+
+class XOSXOSComponent(XOSResource):
+    provides = "tosca.nodes.Component"
+    xos_model = XOSComponent
+    copyin_props = ["name", "image", "command", "ports"]
+
+
+class XOSXOSComponentLink(XOSResource):
+    provides = "tosca.nodes.ComponentLink"
+    xos_model = XOSComponentLink
+    copyin_props = ["container", "alias", "kind"]
+    name_field = "container"
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSXOSComponentLink, self).get_xos_args()
+
+        component_name = self.get_requirement("tosca.relationships.LinkOfComponent", throw_exception=throw_exception)
+        if component_name:
+            args["component"] = self.get_xos_object(XOSComponent, throw_exception=throw_exception, name=component_name)
+
+        return args
+
+
+class XOSXOSComponentVolume(XOSResource):
+    provides = "tosca.nodes.ComponentVolume"
+    xos_model = XOSComponentVolume
+    copyin_props = ["host_path", "read_only"]
+    name_field = "container_path"
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSXOSComponentVolume, self).get_xos_args()
+
+        component_name = self.get_requirement("tosca.relationships.VolumeOfComponent", throw_exception=throw_exception)
+        if component_name:
+            args["component"] = self.get_xos_object(XOSComponent, throw_exception=throw_exception, name=component_name)
+
+        return args