NEW! Mirantis Academy -   Learn confidently with expert guidance and On-demand content.   Learn More


Develop Cloud Applications for OpenStack on Murano, Day 4: The application, part 2: Creating the Murano App

Nick Chase - October 07, 2016
So far in this series, we've explained what Murano is, created an OpenStack cluster with Murano, and built the main script that will install our application. Now it's time to actually package PloneServerApp up for Murano.
In this series, we're looking at a very basic example, and we'll tell you all you need to make it work, but there are some great tutorials and references that describe this process (and more) in detail.  You can find them in the official Murano documentation:So before we move on, let's just distill that down to the basics.

What we're ultimately trying to do

When we're all finished, what we want is basically a *.zip file structured in a way that Murano expects, with files that provide all of the information that it needs. There's nothing really magical about this process, it's just a matter of creating the various resources.  In general, the structure of a Murano application looks something like this:
  |_  Classes
  |   |_  PloneServer.yaml
  |_  Resources
  |   |_  scripts
  |       |_
  |   |_  DeployPloneServer.template
  |_  UI
  |   |_  ui.yaml
  |_  logo.png
  |_  manifest.yaml
Obviously the filenames (and content!) will depend on your specific application, but you get the idea. (If you'd like to see the finished version of this application, you can get it from GitHub.)
When we've assembled all of these pieces, we'll zip them up and they'll be ready to import into Murano.
Let's take a look at the individual pieces.

The individual files in a Murano package

Each of the individual files we're working with is basically just a text file.

The Manifest file

The manifest.yaml file contains the main application’s information. For our PloneServerApp, that means the following:
1.  #  Plone uses GPL version 2 as its license. As of summer 2009, there are
2. #  no active plans to upgrade to GPL version 3.
3. #  You may obtain a copy of the License at
4. #
5. #
6. #
8. Format: 1.3
9. Type: Application
10. FullName: org.openstack.apps.plone.PloneServer
11. Name: Plone CMS
12. Description: |
13. The Ultimate Open Source Enterprise CMS.
14. The Plone CMS is one of the most secure
15. website systems available. This installer
16. lets you deploy Plone in standalone mode.
17. Requires Ubuntu 14.04 image with
18. preinstalled murano-agent.
19. Author: 'Evgeniy Mashkin'
20. Tags: [CMS, WCM]
21. Classes:
22. org.openstack.apps.plone.PloneServer: PloneServer.yaml
Let’s start at Line 8:
Format: 1.3
The versioning of the manifest format is directly connected with YAQL and the version of Murano itself. See the short description of format versions and choose the format version according to the OpenStack release you going to develop your application for. In our case, we're using Mirantis OpenStack 9.0, which is built on the Mitaka OpenStack release, so I chose the 1.3 version that corresponds to Mitaka.
Now let’s move to Line 10:
FullName: org.openstack.apps.plone.PloneServer
Here you're adding a fully qualified name for your application, including the namespace if your choice.
IMPORTANT: Don't use the io.murano namespace for your apps; it's being used for the Murano Core Library.
Lines 11 through 20 show the Name, Description, Author and Tags, which will be shown in the UI:
Name: Plone CMS

Description: |
The Ultimate Open Source Enterprise CMS. The Plone CMS is one of the most secure website systems available. This installer lets you deploy Plone in standalone mode. Requires Ubuntu 14.04 image with preinstalled murano-agent. Author: 'Evgeniy Mashkin' Tags: [CMS, WCM]
Finally, on lines 21 and 22, you'll point to your application class file (which we'll build later). This file should be in the Classes directory of the package.
 org.openstack.apps.plone.PloneServer: PloneServer.yaml
Make sure to double check all of your references, filenames, and whitespaces as errors with these can cause errors when you upload your application package to Murano.

Execution Plan Template

The execution plan template -- DeployPloneServer.template -- describes the installation process of the Plone Server on a virtual machine and contains instructions to the murano-agent on what should be executed to deploy the application. Essentially, it tells Murano how to handle the script we created yesterday.
Here's the DeployPloneServer.template listing for our PloneServerApp:
1.  #  Plone uses GPL version 2 as its license. As of summer 2009, there are
2.  #  no active plans to upgrade to GPL version 3.
3.  #  You may obtain a copy of the License at
4.  #
5.  #
6.  #
7.  FormatVersion: 2.0.0
8.  Version: 1.0.0
9.  Name: Deploy Plone
10. Parameters:
11.   pathname: $pathname
12.   password: $password
13.   port: $port
14. Body: |
15.   return ploneDeploy('{0} {1} {2}'.format(args.pathname, args.password, args.port)).stdout
16. Scripts:
17.   ploneDeploy:
18.     Type: Application
19.     Version: 1.0.0
20.     EntryPoint:
21.     Files: []
22.     Options:
23.       captureStdout: true
24.       captureStderr: true
Starting with lines 12 through 15, you can see that we're defining our parameters - the installation path, administrative password, and TCP port. Just as we added them on the command line yesterday, we need to tell Murano to ask the user for them.
  pathname: $pathname
  password: $password
  port: $port
In the Body section we have a string that describes the Python statement to execute, and how it will be executed by the Murano agent on the virtual machine:
Body: |
  return ploneDeploy('{0} {1} {2}'.format(args.pathname, args.password, args.port)).stdout
Scripts defined in the Scripts section are invoked from here, so, we need to keep the order of arguments consistent with the script that we developed yesterday.
Also, double check all filenames, whitespaces, and brackets. Mistakes here can cause the Murano agent to experience errors when it tries to run our installation script. If you do experience errors in this case, after  an error has occurred, connect to the spawned VM via SSH and check the runPloneDeploy.log file we added for just this purpose.

Dynamic UI form definition

In order for the user to be able to add parameters such as the administrative password, we need to make sure that the user interface is set up correctly.  We do this with the UI.yaml file, which contains the UI forms description that will be shown to users and tells users where they can set available installation options. The ui.yaml file for our PloneServerApp reads as follows:
1.  #  Plone uses GPL version 2 as its license. As of summer 2009, there are
2.  #  no active plans to upgrade to GPL version 3.
3.  #  You may obtain a copy of the License at
4.  #
5.  #
6.  #
7.  Version: 2.3
8.  Application:
9.    ?:
10.     type: org.openstack.apps.plone.PloneServer
11.   pathname: $.appConfiguration.pathname
12.   password: $.appConfiguration.password
13.   port: $.appConfiguration.port
14.   instance:
15.     ?:
16.       type: io.murano.resources.LinuxMuranoInstance
17.     name: generateHostname($.instanceConfiguration.unitNamingPattern, 1)
18.     flavor: $.instanceConfiguration.flavor
19.     image: $.instanceConfiguration.osImage
20.     keyname: $.instanceConfiguration.keyPair
21.     availabilityZone: $.instanceConfiguration.availabilityZone
22.     assignFloatingIp: $.appConfiguration.assignFloatingIP
23.  Forms:
24.    - appConfiguration:
25.        fields:
26.          - name: license
27.            type: string
28.            description: GPL License, Version 2
29.            hidden: true
30.            required: false
31.          - name: pathname
32.            type: string
33.            label: Installation pathname
34.            required: false
35.            initial: '/opt/plone/'
36.            description: >-
37.              Use to specify the top-level path for installation.
38.          - name: password
39.            type: string
40.            label: Admin password
41.            required: false
42.            initial: 'admin'
43.            description: >-
44.              Enter administrative password for Plone.
45.          - name: port
46.            type: string
47.            label: Port
48.            required: false
49.            initial: '8080'
50.            description: >-
51.              Specify the port that Plone will listen to
52.              on available network interfaces.
53.          - name: assignFloatingIP
54.            type: boolean
55.            label: Assign Floating IP
56.            description: >-
57.               Select to true to assign floating IP automatically.
58.            initial: false
59.            required: false
60.          - name: dcInstances
61.            type: integer
62.            hidden: true
63.            initial: 1
64.    - instanceConfiguration:
65,        fields:
66.          - name: title
67.            type: string
68.            required: false
69.            hidden: true
70.            description: Specify some instance parameters on which the application would be created\
71.          - name: flavor
72.            type: flavor
73.            label: Instance flavor
74.            description: >-
75.              Select registered in Openstack flavor. Consider that
76.              application performance depends on this parameter
77.            requirements:
78.              min_vcpus: 1
79.              min_memory_mb: 256
80.            required: false
81.          - name: minrequirements
82.            type: string
83.            label: Minumum requirements
84.            description: |
85.              - Minimum 256 MB RAM and 512 MB of swap space per Plone site
86.              - Minimum 512 MB hard disk space
87.            hidden: true
88.            required: false
89.          - name: recrequirements
90.            type: string
91.            label: Recommended
92.            description: |
93.              - 2 GB or more RAM per Plone site
94.              - 40 GB or more hard disk space
95.            hidden: true
96.            required: false
97.          - name: osImage
98.            type: image
99.            imageType: linux
100.           label: Instance image
101.           description: >-
102.             Select a valid image for the application. The image
103.             should already be prepared and registered in Glance
104.         - name: keyPair
105.           type: keypair
106.           label: Key Pair
107.           description: >-
108.             Select the Key Pair to control access to instances. You can login to
109.             instances using this KeyPair after the deployment of application.
110.           required: false
111.         - name: availabilityZone
112.           type: azone
113.           label: Availability zone
114.           description: Select availability zone where the application would be installed.
115.           required: false
116.         - name: unitNamingPattern
117.           type: string
118.           label: Instance Naming Pattern
119.           required: false
120.           maxLength: 64
121.           regexpValidator: '^[a-zA-z][-_\w]*$'
122.           errorMessages:
123.             invalid: Just letters, numbers, underscores and hyphens are allowed.
124.           helpText: Just letters, numbers, underscores and hyphens are allowed.
125.           description: >-
126.             Specify a string, that will be used in instance hostname.
127.             Just A-Z, a-z, 0-9, dash and underline are allowed.
This is a pretty long file, but it's not as complicated as it looks.
Starting at line 8:
Version: 2.3
The format version for the UI definition is optional and its default value is the latest supported version. If you want to use your application with one of the previous versions you may need to set the version field explicitly.
Moving down the file, we basically have two UI forms: appConfiguration and instanceConfiguration.
Each form contains list of parameters that will be present on it. We place all of the parameters related to our Plone Server application on the appConfiguration form, including the path, password and TCP Port. This will then be sent to the Murano agent to invoke the script:
        - name: pathname
          type: string
          label: Installation pathname
          required: false
          initial: '/opt/plone/'
          description: >-
            Use to specify the top-level path for installation.
        - name: password
          type: string
          label: Admin password
          required: false
          initial: 'admin'
          description: >-
            Enter administrative password for Plone.
        - name: port
          type: string
          label: Port
          required: false
          initial: '8080'
          description: >-
            Specify the port that Plone will listen to
            on available network interfaces.
For each parameter we also set initial values that will be used as defaults.
On the instanceConfiguration form, we’ll place all of the parameters related to instances that will be spawned during deployment. We need to set hardware limitations, such as minimum hardware requirements, in the requirements section:
        - name: flavor
          type: flavor
          label: Instance flavor
          description: >-
            Select registered in Openstack flavor. Consider that
            application performance depends on this parameter
            min_vcpus: 1
            min_memory_mb: 256
          required: false
Also, we need to add notices for users about minimum and recommended Plone hardware requirements on the UI form:
        - name: minrequirements
          type: string
          label: Minumum requirements
          description: |
            - Minimum 256 MB RAM and 512 MB of swap space per Plone site
            - Minimum 512 MB hard disk space
          hidden: true
          required: false
        - name: recrequirements
          type: string
          label: Recommended
          description: |
            - 2 GB or more RAM per Plone site
            - 40 GB or more hard disk space

Murano PL Class Definition

Perhaps the most complicated part of the application is the class definition.  Contained in PloneServer.yaml, it describes the methods the Murano agent must be able to execute in order to manage the application. In this case, the application class looks like this:
1.  #  Plone uses GPL version 2 as its license. As of summer 2009, there are
2.  #  no active plans to upgrade to GPL version 3.
3.  #  You may obtain a copy of the License at
4.  #
5.  #
6.  #
7.  Namespaces:
8.    =: org.openstack.apps.plone
9.    std: io.murano
10.   res: io.murano.resources
11.   sys: io.murano.system
12. Name: PloneServer
13. Extends: std:Application
14. Properties:
15.   instance:
16.     Contract: $.class(res:Instance).notNull()
17.   pathname:
18.     Contract: $.string()
19.   password:
20.     Contract: $.string()
21.   port:
22.     Contract: $.string()
23. Methods:
24.   .init:
25.     Body:
26.       - $._environment: $.find(std:Environment).require()
27.   deploy:
28.     Body:
29.       - If: not $.getAttr(deployed, false)
30.         Then:
31.           - $$this, 'Creating VM for Plone Server.')
32.           - $securityGroupIngress:
33.             - ToPort: 80
34.               FromPort: 80
35.               IpProtocol: tcp
36.               External: true
37.             - ToPort: 443
38.               FromPort: 443
39.               IpProtocol: tcp
40.               External: true
41.             - ToPort: $.port
42.               FromPort: $.port
43.               IpProtocol: tcp
44.               External: true
45.           - $._environment.securityGroupManager.addGroupIngress($securityGroupIngress)
46.           - $.instance.deploy()
47.           - $resources: new(sys:Resources)
48.           - $template: $resources.yaml('DeployPloneServer.template').bind(dict(
49.                 pathname => $.pathname,
50.                 password => $.password,
51.                 port => $.port
52.               ))
53.           - $$this, 'Instance is created. Deploying Plone')
54.           - $$template, $resources)
55.           - $$this, 'Plone Server is installed.')
56.           - If: $.instance.assignFloatingIp
57.             Then:
58.               - $host: $.instance.floatingIpAddress
59.             Else:
60.               - $host: $.instance.ipAddresses.first()
61.           - $$this, format('Plone Server is available at http://{0}:{1}', $host, $.port))
62.           - $.setAttr(deployed, true)
First we set the namespaces and class name, then define the parameters we'll be using later. We can then move into methods.
Besides the standard init method, our PloneServer class has one main method - deploy. It sets up instances of spawning and configuration. The deploy method performs the following tasks:
  1. It configures a security group and opens the TCP port 80, SSH port and our custom TCP port (as determined by the user):
              - $securityGroupIngress:
                - ToPort: 80
                  FromPort: 80
                  IpProtocol: tcp
                  External: true
                - ToPort: 443
                  FromPort: 443
                  IpProtocol: tcp
                  External: true
                - ToPort: $.port
                  FromPort: $.port
                  IpProtocol: tcp
                  External: true
  2. It initiates the spawning of a new virtual machine:
             - $.instance.deploy()
  3. It creates a Resources object, then loads the execution plan template (in the Resources directory) into it, updating the plan with parameters taken from the user:
              - $resources: new(sys:Resources)
              - $template: $resources.yaml('DeployPloneServer.template').bind(dict(
                    pathname => $.pathname,
                    password => $.password,
                    port => $.port
  4. It sends the ready-to-execute-plan to the murano agent:
              - $$template, $resources)
  5. Lastly, it assigns a floating IP  to the newly spawned machine, if it was chosen:
              - If: $.instance.assignFloatingIp
                  - $host: $.instance.floatingIpAddress
                  - $host: $.instance.ipAddresses.first()
Before we move on, just a few words about floating IPs - I will provide you with the key points from Piotr Siwczak’s article  “Configuring Floating IP addresses for Networking in OpenStack Public and Private Clouds”:
“The floating IP mechanism, besides exposing instances directly to the Internet, gives cloud users some flexibility. Having “grabbed” a floating IP from a pool, they can shuffle them (i.e., detach and attach them to different instances on the fly) thus facilitating new code releases and system upgrades. For sysadmins it poses a potential security risk, as the underlying mechanism (iptables) functions in a complicated way and lacks proper monitoring from the OpenStack side.”
Be aware that OpenStack is rapidly changing and some article’s statements may become obsolete, but the point is that there are advantages and disadvantages of using floating IPs.

Image File

In order to use OpenStack, you generally need an image to serve as the template for VMs you spawn. In some cases, those images will already be part of your cloud, but if not, you can specify them in the image.lst file. When you mention any image in this file and put it in your package, the image will be uploaded to your Cloud automatically. When importing images from the image.lst file, the client simply searches for a file with the same name as the name attribute of the image in the images directory of the package.  
An image file is optional, but to make sure your Murano App works you need to point any image with a pre-installed Murano agent. In our case it is Ubuntu 14.04 with a preinstalled Murano agent:
- Name: 'ubuntu-14.04-m-agent.qcow2'
 Hash: '393d4f2a7446ab9804fc96f98b3c9ba1'  Meta:    title: 'Ubuntu 14.04 x64 (pre-installed murano-agent)'    type: 'linux'  DiskFormat: qcow2  ContainerFormat: bare

Application Logo

The logo.png file is a preview image that will be visible to users in the application catalog. Having a logo file is optional, but for now, let’s choose this one:

Create a Package

Finally, now that all the files are ready go to our package files directory (where the manifest.yaml file is placed) we can create a .zip package:
$ zip -r *
Tomorrow we'll wrap up by showing you how to add your new package to the Murano application catalog.

Choose your cloud native journey.

Whatever your role, we’re here to help with open source tools and world-class support.


Subscribe to our bi-weekly newsletter for exclusive interviews, expert commentary, and thought leadership on topics shaping the cloud native world.