34344fe77c2128f319dc77b478b754f2210a3b9a
[plcapi.git] / PLC / Methods / ConfigureDummynetBox.py
1 #
2 # Marta Carbone - UniPi
3 # $Id$
4 #
5 # This script allows to configure a dummynet box from the central site.
6 #
7 # The authentication is done by a dummynet box key
8 # located on the Central Site /usr/share/dummynet/dbox_key
9 # The public key will be propagated to the dummynet boxes
10 # by the same script used for users.
11
12 from PLC.Faults import *                        # faults library
13 from PLC.Method import Method                   # base class used for methods
14 from PLC.Parameter import Parameter, Mixed      # define input parameters
15 from PLC.DummyBoxes import DummyBox, DummyBoxes # main class for a DummyBox
16 from PLC.Sites import Site, Sites               # manage authentication
17 from PLC.Auth import Auth                       # import Auth parameter
18 from PLC.Nodes import Node, Nodes               # main class for Nodes
19 from PLC.NodeNetworks import *                  # main class for Nodes
20 from PLC.Persons import Person, Persons         # main class for Persons
21 from PLC.Slices import Slice, Slices            # main class for Slices
22 import os                                       # ssh
23
24 # basename for the Dummynet box public key
25 # used to send configuration requests
26 # to the dummynet box.
27 DBOX_KNOWN_HOSTS="/var/tmp/.dbox_known_hosts"
28 DBOX_KEY="/usr/share/dummynet/dbox_key"
29
30 DEFAULT_TIMEOUT= "1d"
31
32 # return 1 if the value is present in the array
33 def match(list, value):
34         for i in list:
35                 if (value == i):
36                         return 1
37         return 0
38
39 # if the field key is set in the fields_list dict,
40 # return the cmd_keyword string and the dict value
41 def pipe_build(fields_list, field, cmd_keyword):
42         try:
43                 a = fields_list[field]
44         except:
45                 return ""
46         else:
47                 return cmd_keyword + str(a)
48
49 class ConfigureDummynetBox(Method):
50     """
51     This script will configure an emulated link.
52
53     To do this it:
54     - takes as input the node_id, the slicename,
55       and other dummynet configuration parameters;
56     - make some permission checks;
57     - retrive other useful information from the database;
58     - send the emulated link configuration to the DummynetBox;
59     - wait for the remote execution and return the reply to the user.
60     """
61
62     roles = ['admin', 'pi', 'tech', 'user']
63     link_config = {
64         'node_id': Parameter(int, "Node identifier"),
65         'slicename': Parameter(str, "Name of the slice"),
66         'port': Parameter(int, "Port number"),
67         'bw': Parameter(str, "dummynet, bandwidth limit"),
68         'delay': Parameter(str, "dummynet, delay"),
69         'plr': Parameter(str, "dummynet, packet loss rate"),
70         'upload_extra-delay': Parameter(str, "dummynet, extra delay file"),
71         'extra-delay': Parameter(str, "dummynet, extra delay, if above unset"),
72         'timeout': Parameter(str, "Rules timeout"),
73         'noerror': Parameter(str, "dummynet, if '0' do not propagate errors to the upper layer"),
74     }
75
76     accepts = [
77         Auth(),
78         link_config,
79     ]
80
81     returns = Parameter(str, 'Remote command execution output');
82
83     def call(self, auth, link_config):
84
85         # Only a person istance can send configuration commands
86         if not isinstance(self.caller, Person):
87                 return "1 Authentication method not allowed to perform this operation";
88
89         # Get identity
90         email = str(self.caller['email']);
91
92         # Get mandatory arguments
93         node_id = link_config['node_id']
94         slicename = link_config['slicename']
95         port = link_config['port']
96
97         # Check not mandatory, they will be done again on the dummynet box
98         if port <= 1024:
99                 return "1 Port should be > 1024"
100
101         # Get the dummynet box connected to this node
102         nodes = Nodes(self.api, {'node_id': node_id}, ['dummybox_id','nodenetwork_ids'])
103         if not nodes:
104                 return "1 Node not present"
105
106         dummybox_id = nodes[0]['dummybox_id']
107
108         if (dummybox_id == 0):          # dummybox_id == 0 means empty
109                 return "1 This node has no dummynet box connected"
110
111         dummyboxes = DummyBoxes(self.api, {'dummybox_id': dummybox_id}, ['ip'])
112         dbox_ip = str(dummyboxes[0]['ip'])
113
114         # Get the node ip address, we need to cross with the NodeNetworks table
115         nodenetwork_id = NodeNetworks(self.api, {'node_id': node_id, 'is_primary':'t'}, ['ip']);
116         if not nodenetwork_id or not nodenetwork_id[0]['ip']:
117                 return "1 Network not configured on this node"
118                 
119         node_ip = nodenetwork_id[0]['ip']
120
121         # Search the person_id
122         person_id = Persons(self.api, {'email': email}, ['person_id'])
123         if not person_id:
124                 return "1 User not found"
125         
126         # Search slice information
127         slices = Slices(self.api, {'name': slicename}, ['node_ids', 'person_ids'])
128         if not slices:
129                 return "1 No slices found"
130
131         # Check for permissions:
132         # - the person_id should own the slice
133         # - the slice should be istantiated on the node
134         if not match(slices[0]['node_ids'], node_id) or \
135                         not match(slices[0]['person_ids'], person_id[0]['person_id']):
136                 return "1 The slice %s and the user %s should be istantiated on the node %s" % \
137                                 (slicename,  person_id[0]['person_id'], node_id)
138
139         # Manage the profile upload
140         # if upload_extra-delay is present, we upload the file and use it as profile
141         cmd_line = ""
142         file_to_upload = pipe_build(link_config, "upload_extra-delay", "")
143         if file_to_upload:
144                 cmd_line += "cat " + file_to_upload + " | "
145
146         # start to build the command line
147         # The ssh commands need to use a known hosts file.
148         # Since it is called by different programs, each one
149         # with a different home location, we use some option
150         # to force the known hosts file to be always the same.
151         # We make the command not interactive too.
152         SSH = "ssh -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=yes ";
153         SSH += "-o UserKnownHostsFile=" + "'"+DBOX_KNOWN_HOSTS+"'" + " ";
154
155         # the ssh command takes as input
156         # the node_ip, the slicename, the port number
157         # a timeout and a filename (0 don't upload)
158         cmd_line += SSH + " -i "+DBOX_KEY+" user@"+dbox_ip;
159         cmd_line += " "+str(node_ip)+" "+slicename+" "+str(port);
160
161         # add the timeout
162         cmd = pipe_build(link_config, "timeout", "")
163         cmd_line += " "
164         if not cmd:
165                 cmd_line += DEFAULT_TIMEOUT
166         else:
167                 cmd_line += cmd
168
169         # add the filename to upload, "0" if not defined
170         cmd_line += " "
171         if file_to_upload:
172                 cmd_line += os.path.basename(file_to_upload)
173         else:
174                 cmd_line += "0"
175
176         # add the extra-delay parameter, only the basename
177         # note that the upload_extra-delay, if present win
178         if file_to_upload:
179                 link_config['upload_extra-delay'] = os.path.basename(link_config['upload_extra-delay']); 
180                 cmd_line += pipe_build(link_config, "upload_extra-delay", " extra-delay ")
181         else:
182                 link_config['extra-delay'] = os.path.basename(link_config['extra-delay']); 
183                 cmd_line += pipe_build(link_config, "extra-delay", " extra-delay ")
184
185         # add plr, bw, delay, noerror
186         cmd_line += pipe_build(link_config, "plr", " plr ")
187         cmd_line += pipe_build(link_config, "bw", " bw ")
188         cmd_line += pipe_build(link_config, "delay", " delay ")
189
190         # noerror should be malipulated in a different way
191         noerror = pipe_build(link_config, "noerror", "")
192         if noerror == '0':
193                 cmd_line += " noerror"
194
195         # send the command to the dummynetbox
196         # get output and return code
197         command = os.popen(cmd_line);
198         output = command.read();
199         ret = str(command.close());
200
201         if (ret == "None"):
202                 ret = "0 "
203         else:
204                 ret = "1 "
205
206         ret += "The command line used to configure the link follows:\n"
207         ret += cmd_line
208         ret += " \n" + output
209
210         return ret