Simplify/correct network args in server create
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 18 Dec 2013 15:00:01 +0000 (17:00 +0200)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 18 Dec 2013 15:02:35 +0000 (17:02 +0200)
Refs: #4563

In server create, replace network arguments with only two:

--network=[id=]NETWORK_ID[,[ip=]IP]
--no-network  (flag)

There arguments are mutually exclussive

docs/examplesdir/astakos.rst
docs/examplesdir/server.rst
kamaki/cli/commands/cyclades.py

index 3572f32..cd65dd1 100644 (file)
@@ -5,7 +5,7 @@ Astakos
 is the synnefo implementation of a variant of OpenStack Keystone with custom
 extentions. Kamaki offer tools for managing Astakos information.
 
-.. node:: The underlying library that calls the API is part of the synnefo
+.. note:: The underlying library that calls the API is part of the synnefo
     and it is called 'astakosclient'
 
 User
index 001322c..635e624 100644 (file)
@@ -47,7 +47,7 @@ List available images
     f1r57-1m4g3-1d Debian Base Alpha
     53c0nd-1m4g3-1d Beta Debian Base
 
-Let's pick the `C1R128D1drbd` (id: 1) flavor and the `Debian Base Alpha` (id:
+Pick the `C1R128D1drbd` (id: 1) flavor and the `Debian Base Alpha` (id:
 f1r57-1m4g3-1d) image to create a new virtual server called 'My First Server'
 
 .. code-block:: console
@@ -101,6 +101,47 @@ Destroy the virtual server (wait is still optional)
     <bar showing destruction progress, until 100%>
     Server 141 is now in DELETED mode
 
+Create Servers with networks
+----------------------------
+
+First, check the available IPs:
+
+.. code-block:: console
+
+    $ kamaki ip list
+    42042
+        instance_id: 424242
+        floating_network_id: 1
+        fixed_ip_address: None
+        floating_ip_address: 123.456.78.90
+        port_id: 24024
+
+So, there is an ip (123.456.78.90) on network 1. We can use it:
+
+.. code-block:: console
+
+    $ kamaki server create --network=1,123.456.78.90 --name='Extrovert Server' --flavor-id=1 --image-id=f1r57-1m4g3-1d
+    ...
+
+Another case it the connection to a private network (so, no IP):
+
+.. code-block:: console
+
+    $ kamaki network list
+    1   Public network
+    7   A custom private network
+    9   Another custom private network
+
+    $ kamaki server create --network=7 --name='Introvert Server' --flavor-id=1 --image-id=f1r57-1m4g3-1d
+
+.. note:: Multiple *- -network* args will create a corresponding number of
+    connections (nics) to the specified networks.
+
+.. note:: Ommiting *- -network* will let the cloud apply the default network
+    policy. To create a server without any connections (nics), use the
+    *- -no-network argument*
+
+
 Inject ssh keys to a debian server
 ----------------------------------
 
@@ -134,7 +175,10 @@ Create a virtual server while injecting current user public key to root account
 
 .. code-block:: console
 
-    $ kamaki server create --name='NoPassword Server' --flavor-id=1 --image-id=f1r57-1m4g3-1d -p /home/someuser/.ssh/id_rsa.pub,/root/.ssh/authorized_keys
+    $ kamaki server create --name='NoPassword Server' --flavor-id=1 --image-id=f1r57-1m4g3-1d \
+        --network=1,123.456.78.90 \
+        -p /home/someuser/.ssh/id_rsa.pub,/root/.ssh/authorized_keys
+
     accessIPv4:
     accessIPv6:
     addresses:
@@ -159,8 +203,8 @@ Create a virtual server while injecting current user public key to root account
     updated:         2013-06-19T12:34:48.512867+00:00
     user_id:         s0m3-u53r-1d
 
-When the virtual server is ready, get the virtual servers external IP from the
-web UI. Let's assume the IP is 123.456.78.90 .
+When the server is ready, we can connect through the public network 1 and the
+IP 123.456.78.90 :
 
 .. code-block:: console
 
index afb6a0c..44a3a4b 100644 (file)
@@ -375,24 +375,46 @@ class PersonalityArgument(KeyValueArgument):
                                 '%s' % ve])
 
 
-class NetworkIpArgument(RepeatableArgument):
+class NetworkArgument(RepeatableArgument):
+    """[id=]NETWORK_ID[,[ip=]IP]"""
 
     @property
     def value(self):
-        return getattr(self, '_value', [])
+        return getattr(self, '_value', self.default)
 
     @value.setter
     def value(self, new_value):
-        for v in (new_value or []):
-            net_and_ip = v.split(',')
-            if len(net_and_ip) < 2:
+        for v in new_value or []:
+            part1, sep, part2 = v.partition(',')
+            netid, ip = '', ''
+            if part1.startswith('id='):
+                netid = part1[len('id='):]
+            elif part1.startswith('ip='):
+                ip = part1[len('ip='):]
+            else:
+                netid = part1
+            if part2:
+                if (part2.startswith('id=') and netid) or (
+                        part2.startswith('ip=') and ip):
+                    raise CLIInvalidArgument(
+                        'Invalid network argument %s' % v, details=[
+                        'Valid format: [id=]NETWORK_ID[,[ip=]IP]'])
+                if part2.startswith('id='):
+                    netid = part2[len('id='):]
+                elif part2.startswith('ip='):
+                    ip = part2[len('ip='):]
+                elif netid:
+                    ip = part2
+                else:
+                    netid = part2
+            if not netid:
                 raise CLIInvalidArgument(
-                    'Value "%s" is missing parts' % v,
-                    details=['Correct format: %s NETWORK_ID,IP' % (
-                        self.parsed_name[0])])
-            self._value = getattr(self, '_value', list())
-            self._value.append(
-                dict(uuid=net_and_ip[0], fixed_ip=net_and_ip[1]))
+                    'Invalid network argument %s' % v, details=[
+                    'Valid format: [id=]NETWORK_ID[,[ip=]IP]'])
+            self._value = getattr(self, '_value', [])
+            self._value.append(dict(uuid=netid))
+            if ip:
+                self._value[-1]['fixed_ip'] = ip
 
 
 @command(server_cmds)
@@ -413,25 +435,26 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
             '--cluster-size'),
         max_threads=IntArgument(
             'Max threads in cluster mode (default 1)', '--threads'),
-        network_id=RepeatableArgument(
-            'Connect server to network (can be repeated)', '--network'),
-        network_id_and_ip=NetworkIpArgument(
-            'Connect server to network w. floating ip ( NETWORK_ID,IP )'
-            '(can be repeated)',
-            '--network-with-ip'),
-        automatic_ip=FlagArgument(
-            'Automatically assign an IP to the server', '--automatic-ip')
+        network_configuration=NetworkArgument(
+            'Connect server to network: [id=]NETWORK_ID[,[ip=]IP]        . '
+            'Use only NETWORK_ID for private networks.        . '
+            'Use NETWORK_ID,[ip=]IP for networks with IP.        . '
+            'Can be repeated, mutually exclussive with --no-network',
+            '--network'),
+        no_network=FlagArgument(
+            'Do not create any network NICs on the server.        . '
+            'Mutually exclusive to --network        . '
+            'If neither --network or --no-network are used, the default '
+            'network policy is applied. This policy is configured on the '
+            'cloud and kamaki is oblivious to it',
+            '--no-network')
     )
     required = ('server_name', 'flavor_id', 'image_id')
 
     @errors.cyclades.cluster_size
     def _create_cluster(self, prefix, flavor_id, image_id, size):
-        if self['automatic_ip']:
-            networks = []
-        else:
-            networks = [dict(uuid=netid) for netid in (
-                self['network_id'] or [])] + (self['network_id_and_ip'] or [])
-            networks = networks or None
+        networks = self['network_configuration'] or (
+            None if self['no_network'] else [])
         servers = [dict(
             name='%s%s' % (prefix, i if size > 1 else ''),
             flavor_id=flavor_id,
@@ -483,14 +506,12 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
 
     def main(self):
         super(self.__class__, self)._run()
-        if self['automatic_ip'] and (
-                self['network_id'] or self['network_id_and_ip']):
-            raise CLIInvalidArgument('Invalid argument combination', details=[
-                'Argument %s should not be combined with other' % (
-                    self.arguments['automatic_ip'].lvalue),
-                'network-related arguments i.e., %s or %s' % (
-                    self.arguments['network_id'].lvalue,
-                    self.arguments['network_id_and_ip'].lvalue)])
+        if self['no_network'] and self['network']:
+            raise CLIInvalidArgument(
+                'Invalid argument compination', importance=2, details=[
+                'Arguments %s and %s are mutually exclusive' % (
+                    self.arguments['no_network'].lvalue,
+                    self.arguments['network'].lvalue)])
         self._run(
             name=self['server_name'],
             flavor_id=self['flavor_id'],