server-create.py 7.59 KB
Newer Older
Vangelis Koukis's avatar
Vangelis Koukis committed
1
# Copyright (C) 2010-2014 GRNET S.A.
2
#
Vangelis Koukis's avatar
Vangelis Koukis committed
3 4 5 6
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
7
#
Vangelis Koukis's avatar
Vangelis Koukis committed
8 9 10 11
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
12
#
Vangelis Koukis's avatar
Vangelis Koukis committed
13 14
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 16 17

from optparse import make_option

18 19
from django.core.management.base import CommandError

20 21
from synnefo.management import common, pprint
from snf_django.management.utils import parse_bool
22
from snf_django.management.commands import SynnefoCommand
23

24
from synnefo.logic import servers
25

26 27 28 29 30 31 32
HELP_MSG = """

Create a new VM without authenticating the user or checking the resource
limits of the user. Also the allocator can be bypassed by specifing a
backend-id.
"""

33

34
class Command(SynnefoCommand):
35
    help = "Create a new VM." + HELP_MSG
36
    umask = 0o007
37

38
    option_list = SynnefoCommand.option_list + (
39 40 41 42 43 44
        make_option("--backend-id", dest="backend_id",
                    help="Unique identifier of the Ganeti backend."
                         " Use snf-manage backend-list to find out"
                         " available backends."),
        make_option("--name", dest="name",
                    help="An arbitrary string for naming the server"),
45
        make_option("--user", dest="user_id",
46
                    help="Unique identifier of the owner of the server"),
47
        make_option("--image", dest="image_id", default=None,
48 49 50
                    help="Unique identifier of the image."
                         " Use snf-manage image-list to find out"
                         " available images."),
51
        make_option("--flavor", dest="flavor_id",
52 53 54 55
                    help="Unique identifier of the flavor"
                         " Use snf-manage flavor-list to find out"
                         " available flavors."),
        make_option("--password", dest="password",
56
                    help="Password for the new server"),
57 58 59 60
        make_option("--port", dest="connections", action="append",
                    help="--port network:<network_id>(,address=<ip_address>),"
                         " --port id:<port_id>"
                         " --port floatingip:<floatingip_id>."),
61
        make_option("--volume", dest="volumes", action="append",
62
                    help="--volume size=<size>, --volume id=<volume_id>"
63 64 65
                         ", --volume size=<size>,image=<image_id>"
                         ", --volume size=<size>,snapshot=<snapshot_id>",
                    default=[]),
66 67
        make_option("--floating-ips", dest="floating_ip_ids",
                    help="Comma separated list of port IDs to connect"),
68 69 70
        make_option(
            '--wait',
            dest='wait',
71
            default="False",
72 73
            choices=["True", "False"],
            metavar="True|False",
74
            help="Wait for Ganeti job to complete. [Default: False]"),
75

76
    )
77

78
    @common.convert_api_faults
79 80 81 82 83 84 85 86 87 88
    def handle(self, *args, **options):
        if args:
            raise CommandError("Command doesn't accept any arguments")

        name = options['name']
        user_id = options['user_id']
        backend_id = options['backend_id']
        image_id = options['image_id']
        flavor_id = options['flavor_id']
        password = options['password']
89
        volumes = options['volumes']
90 91 92 93

        if not name:
            raise CommandError("name is mandatory")
        if not user_id:
94
            raise CommandError("user is mandatory")
95 96
        if not password:
            raise CommandError("password is mandatory")
97
        if not flavor_id:
98
            raise CommandError("flavor is mandatory")
99
        if not image_id and not volumes:
100
            raise CommandError("image is mandatory")
101

102
        flavor = common.get_resource("flavor", flavor_id)
103 104 105
        if image_id is not None:
            common.get_image(image_id, user_id)

106
        if backend_id:
107
            backend = common.get_resource("backend", backend_id)
108 109
        else:
            backend = None
110

111
        connection_list = parse_connections(options["connections"])
112 113
        volumes_list = parse_volumes(volumes)
        server = servers.create(user_id, name, password, flavor, image_id,
114
                                networks=connection_list,
115
                                volumes=volumes_list,
116 117 118 119 120
                                use_backend=backend)
        pprint.pprint_server(server, stdout=self.stdout)

        wait = parse_bool(options["wait"])
        common.wait_server_task(server, wait, self.stdout)
121 122


123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
def parse_volumes(vol_list):
    volumes = []
    for vol in vol_list:
        vol_dict = {}
        kv_list = vol.split(",")
        for kv_item in kv_list:
            try:
                k, v = kv_item.split("=")
            except (TypeError, ValueError):
                raise CommandError("Invalid syntax for volume: %s" % vol)
            vol_dict[k] = v
        size = vol_dict.get("size")
        if size is not None:
            try:
                size = int(size)
            except (TypeError, ValueError):
                raise CommandError("Invalid size: %s" % size)
        if len(vol_dict) == 1:
            if size is not None:
                volumes.append({"size": size, "source_type": "blank",
                                "source_uuid": None})
                continue
            vol_id = vol_dict.get("id")
            if vol_id is not None:
                volumes.append({"source_uuid": vol_id,
                                "source_type": "volume"})
                continue
            raise CommandError("Invalid syntax for volume %s" % vol)
        image = vol_dict.get("image")
        snapshot = vol_dict.get("snapshot")
        if image and snapshot or not(image or snapshot) or size is None:
            raise CommandError("Invalid syntax for volume %s" % vol)
        source = image if image else snapshot
        source_type = "image" if image else "snapshot"
        volumes.append({"source_uuid": source,
                        "source_type": source_type,
                        "size": size})
    return volumes


163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
def parse_connections(con_list):
    connections = []
    if con_list:
        for opt in con_list:
            try:
                con_kind = opt.split(":")[0]
                if con_kind == "network":
                    info = opt.split(",")
                    network_id = info[0].split(":")[1]
                    try:
                        address = info[1].split(":")[1]
                    except:
                        address = None
                    if address:
                        val = {"uuid": network_id, "fixed_ip": address}
                    else:
                        val = {"uuid": network_id}
                elif con_kind == "id":
                    port_id = opt.split(":")[1]
                    val = {"port": port_id}
                elif con_kind == "floatingip":
                    fip_id = opt.split(":")[1]
185 186
                    fip = common.get_resource("floating-ip", fip_id,
                                              for_update=True)
187 188 189 190 191 192 193 194
                    val = {"uuid": fip.network_id, "fixed_ip": fip.address}
                else:
                    raise CommandError("Unknown argument for option --port")

                connections.append(val)
            except:
                raise CommandError("Malformed information for option --port")
    return connections