9 import powder.rpc as prpc
10 import powder.ssh as pssh
13 class PowderExperiment:
14 """Represents a single powder experiment. Can be used to start, interact with,
15 and terminate the experiment. After an experiment is ready, this object
16 holds references to the nodes in the experiment, which can be interacted
20 experiment_name (str): A name for the experiment. Must be less than 16 characters.
21 project_name (str): The name of the Powder Project associated with the experiment.
22 profile_name (str): The name of an existing Powder profile you want to use for the experiment.
25 status (int): Represents the last known status of the experiment as
26 retrieved from the Powder RPC servqer.
27 nodes (dict of str: Node): A lookup table mapping node ids to Node instances
35 EXPERIMENT_NOT_STARTED = 0
36 EXPERIMENT_PROVISIONING = 1
37 EXPERIMENT_PROVISIONED = 2
43 PROVISION_TIMEOUT_S = 1800
46 def __init__(self, experiment_name, project_name, profile_name):
47 if len(experiment_name) > 16:
48 logging.error('Experiment name {} is too long (cannot exceed {} characters)'.format(experiment_name,
49 self.MAX_NAME_LENGTH))
52 self.experiment_name = experiment_name
53 self.project_name = project_name
54 self.profile_name = profile_name
55 self.status = self.EXPERIMENT_NOT_STARTED
57 self._manifests = None
58 self._poll_count_max = self.PROVISION_TIMEOUT_S // self.POLL_INTERVAL_S
60 logging.info('initialized experiment {} based on profile {} under project {}'.format(experiment_name,
64 def start_and_wait(self):
65 """Start the experiment and wait for READY or FAILED status."""
66 logging.info('starting experiment {}'.format(self.experiment_name))
67 rval, response = prpc.start_experiment(self.experiment_name,
70 if rval == prpc.RESPONSE_SUCCESS:
74 logging.info('self.still_provisioning: {}'.format(self.still_provisioning))
75 while self.still_provisioning and poll_count < self._poll_count_max:
76 logging.info('waiting for provision process done')
78 time.sleep(self.POLL_INTERVAL_S)
80 self.status = self.EXPERIMENT_FAILED
81 logging.info(response)
86 """Terminate the experiment. All allocated resources will be released."""
87 logging.info('terminating experiment {}'.format(self.experiment_name))
88 rval, response = prpc.terminate_experiment(self.project_name, self.experiment_name)
89 if rval == prpc.RESPONSE_SUCCESS:
90 self.status = self.EXPERIMENT_NULL
92 logging.error('failed to terminate experiment')
93 logging.error('output {}'.format(response['output']))
97 def _get_manifests(self):
98 """Get experiment manifests, translate to list of dicts."""
99 rval, response = prpc.get_experiment_manifests(self.project_name,
100 self.experiment_name)
101 if rval == prpc.RESPONSE_SUCCESS:
102 response_json = json.loads(response['output'])
103 self._manifests = [xmltodict.parse(response_json[key]) for key in response_json.keys()]
104 logging.info('got manifests')
106 logging.error('failed to get manifests')
110 def _parse_manifests(self):
111 """Parse experiment manifests and add nodes to lookup table."""
112 for manifest in self._manifests:
113 logging.info('parsed manifest:{}'.format(manifest))
114 nodes = manifest['rspec']['node']
115 logging.info('parsed manifest nodes:{}'.format(nodes))
116 client_id = nodes['@client_id']
117 logging.info('parsed manifest client_id:{}'.format(client_id))
119 logging.info('parsed manifest host:{}'.format(host))
120 hostname = host['@name']
121 logging.info('parsed manifest hostname:{}'.format(hostname))
122 self.ipv4 = host['@ipv4']
123 logging.info('parsed manifest ipv4:{}'.format(self.ipv4))
125 logging.info('parsed manifest node:{}'.format(node))
126 # only need to add nodes with public IP addresses for now
128 # hostname = node['host']['@name']
129 # ipv4 = node['host']['@ipv4']
130 # client_id = nodes['@client_id']
131 # self.nodes[client_id] = Node(client_id=client_id, ip_address=ipv4,
138 def _get_status(self):
139 """Get experiment status and update local state. If the experiment is ready, get
140 and parse the associated manifests.
143 rval, response = prpc.get_experiment_status(self.project_name,
144 self.experiment_name)
145 if rval == prpc.RESPONSE_SUCCESS:
146 output = response['output']
147 #if output == 'Status: ready\n':
148 if "ready" in output:
149 self.status = self.EXPERIMENT_READY
150 self._get_manifests()._parse_manifests()
151 #elif output == 'Status: provisioning\n':
152 elif "provisioning" in output:
153 self.status = self.EXPERIMENT_PROVISIONING
154 #elif output == 'Status: provisioned\n':
155 elif "provisioned" in output:
156 self.status = self.EXPERIMENT_PROVISIONED
157 #elif output == 'Status: failed\n':
158 elif "failed" in output:
159 self.status = self.EXPERIMENT_FAILED
161 logging.info('status is {}'.format(self.status))
162 self.still_provisioning = self.status in [self.EXPERIMENT_PROVISIONING,
163 self.EXPERIMENT_PROVISIONED]
164 logging.info('experiment status is {}'.format(output.strip()))
166 logging.error('failed to get experiment status')
172 """Represents a node on the Powder platform. Holds an SSHConnection instance for
173 interacting with the node.
176 client_id (str): Matches the id defined for the node in the Powder profile.
177 ip_address (str): The public IP address of the node.
178 hostname (str): The hostname of the node.
179 ssh (SSHConnection): For interacting with the node via ssh through pexpect.
182 #def __init__(self, client_id, ip_address, hostname):
183 def __init__(self, ip_address):
184 #self.client_id = client_id
185 self.ip_address = ip_address
186 #self.hostname = hostname
187 self.ssh = pssh.SSHConnection(ip_address=self.ip_address)