C1M5 Object Oriented Programming V7
C1M5 Object Oriented Programming V7
class Server:
def __init__(self):
"""Creates a new server instance, with no active connections."""
self.connections = {}
def load(self):
"""Calculates the current load for all connections."""
total = 0
# Add up the load for each of the connections
for val in self.connections.values():
total += val
return total
def __str__(self):
1
"""Returns a string with the current load of the server"""
return "{:.2f}%".format(self.load())
#End Portion 1#
Now run the following cell to create a Server instance and add a connection to it, then check
the load:
print(server.load())
2.9001341323192222
After running the above code cell, if you get a NameError message, be sure to run the Server
class definition code block first.
The output should be 0. This is because some things are missing from the Server class. So,
you’ll need to go back and fill in the blanks to make it behave properly. Go back to the Server class
definition and fill in the missing parts for the add_connection and load methods to make the cell
above print a number different than zero. As the load is calculated randomly, this number should
be different each time the code is executed. Hint: Recall that you can iterate through the values of
your connections dictionary just as you would any sequence.
Great! If your output is a random number between 1 and 10, you have successfully coded the
add_connection and load methods of the Server class. Well done! What about closing a connec-
tion? Right now the close_connection method doesn’t do anything. Go back to the Server class
definition and fill in the missing code for the close_connection method to make the following
code work correctly:
In [9]: server.close_connection("192.168.1.1")
print(server.load())
You have successfully coded the close_connection method if the cell above prints 0. Hint:
Remember that del dictionary[key] removes the item with key key from the dictionary.
Alright, we now have a basic implementation of the server class. Let’s look at the basic Load-
Balancing class. This class will start with only one server available. When a connection gets added,
it will randomly select a server to serve that connection, and then pass on the connection to the
server. The LoadBalancing class also needs to keep track of the ongoing connections to be able to
close them. This is the basic structure:
2
self.servers = [Server()]
def avg_load(self):
"""Calculates the average load of all servers"""
# Sum the load of each server and divide by the amount of servers
return 0
def ensure_availability(self):
"""If the average load is higher than 50, spin up a new server"""
pass
def __str__(self):
"""Returns a string with the load for each server."""
loads = [str(server) for server in self.servers]
return "[{}]".format(",".join(loads))
#End Portion 2#
As with the Server class, this class is currently incomplete. You need to fill in the gaps to make
it work correctly. For example, this snippet should create a connection in the load balancer, assign
it to a running server and then the load should be more than zero:
In [5]: l = LoadBalancing()
l.add_connection("fdca:83d2::f20d")
print(l.avg_load())
After running the above code, the output is 0. Fill in the missing parts for the add_connection
and avg_load methods of the LoadBalancing class to make this print the right load. Be sure that
the load balancer now has an average load more than 0 before proceeding.
What if we add a new server?
In [6]: l.servers.append(Server())
print(l.avg_load())
3
The average load should now be half of what it was before. If it’s not, make sure you correctly
fill in the missing gaps for the add_connection and avg_load methods so that this code works
correctly. Hint: You can iterate through the all servers in the self.servers list to get the total server
load amount and then divide by the length of the self.servers list to compute the average load
amount.
Fantastic! Now what about closing the connection?
In [7]: l.close_connection("fdca:83d2::f20d")
print(l.avg_load())
Fill in the code of the LoadBalancing class to make the load go back to zero once the connection
is closed. Great job! Before, we added a server manually. But we want this to happen automati-
cally when the average load is more than 50%. To make this possible, fill in the missing code for
the ensure_availability method and call it from the add_connection method after a connection
has been added. You can test it with the following code:
[0.00%,0.00%]
The code above adds 20 new connections and then prints the loads for each server in the load
balancer. If you coded correctly, new servers should have been added automatically to ensure that
the average load of all servers is not more than 50%. Run the following code to verify that the
average load of the load balancer is not more than 50%.
In [9]: print(l.avg_load())
In [3]: help({})
class dict(object)
| dict() -> new empty dictionary
| dict(mapping) -> new dictionary initialized from a mapping object's
| (key, value) pairs
| dict(iterable) -> new dictionary initialized as if via:
| d = {}
| for k, v in iterable:
| d[k] = v
| dict(**kwargs) -> new dictionary initialized with the name=value pairs
4
| in the keyword argument list. For example: dict(one=1, two=2)
|
| Methods defined here:
|
| __contains__(self, key, /)
| True if D has a key k, else False.
|
| __delitem__(self, key, /)
| Delete self[key].
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __getitem__(...)
| x.__getitem__(y) <==> x[y]
|
| __gt__(self, value, /)
| Return self>value.
|
| __init__(self, /, *args, **kwargs)
| Initialize self. See help(type(self)) for accurate signature.
|
| __iter__(self, /)
| Implement iter(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __len__(self, /)
| Return len(self).
|
| __lt__(self, value, /)
| Return self<value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __repr__(self, /)
| Return repr(self).
5
|
| __setitem__(self, key, value, /)
| Set self[key] to value.
|
| __sizeof__(...)
| D.__sizeof__() -> size of D in memory, in bytes
|
| clear(...)
| D.clear() -> None. Remove all items from D.
|
| copy(...)
| D.copy() -> a shallow copy of D
|
| fromkeys(iterable, value=None, /) from builtins.type
| Returns a new dict with keys from iterable and values equal to value.
|
| get(...)
| D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.
|
| items(...)
| D.items() -> a set-like object providing a view on D's items
|
| keys(...)
| D.keys() -> a set-like object providing a view on D's keys
|
| pop(...)
| D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
| If key is not found, d is returned if given, otherwise KeyError is raised
|
| popitem(...)
| D.popitem() -> (k, v), remove and return some (key, value) pair as a
| 2-tuple; but raise KeyError if D is empty.
|
| setdefault(...)
| D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
|
| update(...)
| D.update([E, ]**F) -> None. Update D from dict/iterable E and F.
| If E is present and has a .keys() method, then does: for k in E: D[k] = E[k]
| If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v
| In either case, this is followed by: for k in F: D[k] = F[k]
|
| values(...)
| D.values() -> an object providing a view on D's values
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
6
| __hash__ = None
Awesome! If the average load is indeed less than 50%, you are all done with this assessment.